From 97d7ed7dceca1ce0cb11102e431de2e5a443e66a Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Fri, 11 Nov 2022 11:23:41 +0000 Subject: [PATCH 001/104] Changed decoder list sort to order by functional support of format Added new method to check if codec just functionally supports a format. Changed getDecoderInfosSortedByFormatSupport to use new function to order by functional support. This allows decoders that only support functionally and are more preferred by the MediaCodecSelector to keep their preferred position in the sorted list. UnitTests included -Two MediaCodecVideoRenderer tests that verify hw vs sw does not have an effect on sort of the decoder list, it is only based on functional support Issue: google/ExoPlayer#10604 PiperOrigin-RevId: 487779284 (cherry picked from commit 1eb8a6b36ed98bd66a5bad5273526a918511a39f) --- .../exoplayer2/RendererCapabilities.java | 6 +- .../exoplayer2/mediacodec/MediaCodecInfo.java | 24 +++- .../mediacodec/MediaCodecRenderer.java | 8 ++ .../exoplayer2/mediacodec/MediaCodecUtil.java | 13 +- .../video/MediaCodecVideoRendererTest.java | 121 ++++++++++++++++++ 5 files changed, 152 insertions(+), 20 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java index 04469dedc3..f409cfbd07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java @@ -140,13 +140,13 @@ public interface RendererCapabilities { /** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */ int MODE_SUPPORT_MASK = 0b11 << 7; /** - * The renderer will use a decoder for fallback mimetype if possible as format's MIME type is - * unsupported + * The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME + * type. */ int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7; /** The renderer is able to use the primary decoder for the format's MIME type. */ int DECODER_SUPPORT_PRIMARY = 0b1 << 7; - /** The renderer will use a fallback decoder. */ + /** The format exceeds the primary decoder's capabilities but is supported by fallback decoder */ int DECODER_SUPPORT_FALLBACK = 0; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index 61230b2d0c..d60fde32fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -243,7 +243,8 @@ public final class MediaCodecInfo { } /** - * Returns whether the decoder may support decoding the given {@code format}. + * Returns whether the decoder may support decoding the given {@code format} both functionally and + * performantly. * * @param format The input media format. * @return Whether the decoder may support decoding the given {@code format}. @@ -254,7 +255,7 @@ public final class MediaCodecInfo { return false; } - if (!isCodecProfileAndLevelSupported(format)) { + if (!isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ true)) { return false; } @@ -281,15 +282,24 @@ public final class MediaCodecInfo { } } + /** + * Returns whether the decoder may functionally support decoding the given {@code format}. + * + * @param format The input media format. + * @return Whether the decoder may functionally support decoding the given {@code format}. + */ + public boolean isFormatFunctionallySupported(Format format) { + return isSampleMimeTypeSupported(format) + && isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false); + } + private boolean isSampleMimeTypeSupported(Format format) { return mimeType.equals(format.sampleMimeType) || mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format)); } - private boolean isCodecProfileAndLevelSupported(Format format) { - if (format.codecs == null) { - return true; - } + private boolean isCodecProfileAndLevelSupported( + Format format, boolean checkPerformanceCapabilities) { Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel == null) { // If we don't know any better, we assume that the profile and level are supported. @@ -325,7 +335,7 @@ public final class MediaCodecInfo { for (CodecProfileLevel profileLevel : profileLevels) { if (profileLevel.profile == profile - && profileLevel.level >= level + && (profileLevel.level >= level || !checkPerformanceCapabilities) && !needsProfileExcludedWorkaround(mimeType, profile)) { return true; } 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 0f6bbb3120..161166a1cf 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 @@ -1111,6 +1111,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } codecInitializedTimestamp = SystemClock.elapsedRealtime(); + if (!codecInfo.isFormatSupported(inputFormat)) { + Log.w( + TAG, + Util.formatInvariant( + "Format exceeds selected codec's capabilities [%s, %s]", + Format.toLogString(inputFormat), codecName)); + } + this.codecInfo = codecInfo; this.codecOperatingRate = codecOperatingRate; codecInputFormat = inputFormat; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 9ef5475bb1..16583004fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -188,22 +188,15 @@ public final class MediaCodecUtil { } /** - * Returns a copy of the provided decoder list sorted such that decoders with format support are - * listed first. The returned list is modifiable for convenience. + * Returns a copy of the provided decoder list sorted such that decoders with functional format + * support are listed first. The returned list is modifiable for convenience. */ @CheckResult public static List getDecoderInfosSortedByFormatSupport( List decoderInfos, Format format) { decoderInfos = new ArrayList<>(decoderInfos); sortByScore( - decoderInfos, - decoderInfo -> { - try { - return decoderInfo.isFormatSupported(format) ? 1 : 0; - } catch (DecoderQueryException e) { - return -1; - } - }); + decoderInfos, decoderInfo -> decoderInfo.isFormatFunctionallySupported(format) ? 1 : 0); return decoderInfos; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index fc2fa8fc2e..f8fca4c1bb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; @@ -83,6 +84,32 @@ public class MediaCodecVideoRendererTest { .setHeight(1080) .build(); + private static final MediaCodecInfo H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO = + MediaCodecInfo.newInstance( + /* name= */ "h264-codec-hw", + /* mimeType= */ MimeTypes.VIDEO_H264, + /* codecMimeType= */ MimeTypes.VIDEO_H264, + /* capabilities= */ createCodecCapabilities( + CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel4), + /* hardwareAccelerated= */ true, + /* softwareOnly= */ false, + /* vendor= */ false, + /* forceDisableAdaptive= */ false, + /* forceSecure= */ false); + + private static final MediaCodecInfo H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO = + MediaCodecInfo.newInstance( + /* name= */ "h264-codec-sw", + /* mimeType= */ MimeTypes.VIDEO_H264, + /* codecMimeType= */ MimeTypes.VIDEO_H264, + /* capabilities= */ createCodecCapabilities( + CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel5), + /* hardwareAccelerated= */ false, + /* softwareOnly= */ true, + /* vendor= */ false, + /* forceDisableAdaptive= */ false, + /* forceSecure= */ false); + private Looper testMainLooper; private Surface surface; private MediaCodecVideoRenderer mediaCodecVideoRenderer; @@ -710,6 +737,100 @@ public class MediaCodecVideoRendererTest { .isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY); } + @Test + public void getDecoderInfo_withNonPerformantHardwareDecoder_returnsHardwareDecoderFirst() + throws Exception { + // AVC Format, Profile: 8, Level: 8192 + Format avcFormat = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_H264) + .setCodecs("avc1.64002a") + .build(); + // Provide hardware and software AVC decoders + MediaCodecSelector mediaCodecSelector = + (mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> { + if (!mimeType.equals(MimeTypes.VIDEO_H264)) { + return ImmutableList.of(); + } + // Hardware decoder supports above format functionally but not performantly as + // it supports MIME type & Profile but not Level + // Software decoder supports format functionally and peformantly as it supports + // MIME type, Profile, and Level(assuming resolution/frame rate support too) + return ImmutableList.of( + H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO); + }; + MediaCodecVideoRenderer renderer = + new MediaCodecVideoRenderer( + ApplicationProvider.getApplicationContext(), + mediaCodecSelector, + /* allowedJoiningTimeMs= */ 0, + /* eventHandler= */ new Handler(testMainLooper), + /* eventListener= */ eventListener, + /* maxDroppedFramesToNotify= */ 1); + renderer.init(/* index= */ 0, PlayerId.UNSET); + + List mediaCodecInfoList = + renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false); + @Capabilities int capabilities = renderer.supportsFormat(avcFormat); + + assertThat(mediaCodecInfoList).hasSize(2); + assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isTrue(); + assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED); + assertThat(RendererCapabilities.getDecoderSupport(capabilities)) + .isEqualTo(RendererCapabilities.DECODER_SUPPORT_FALLBACK); + } + + @Test + public void getDecoderInfo_softwareDecoderPreferred_returnsSoftwareDecoderFirst() + throws Exception { + // AVC Format, Profile: 8, Level: 8192 + Format avcFormat = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_H264) + .setCodecs("avc1.64002a") + .build(); + // Provide software and hardware AVC decoders + MediaCodecSelector mediaCodecSelector = + (mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> { + if (!mimeType.equals(MimeTypes.VIDEO_H264)) { + return ImmutableList.of(); + } + // Hardware decoder supports above format functionally but not performantly as + // it supports MIME type & Profile but not Level + // Software decoder supports format functionally and peformantly as it supports + // MIME type, Profile, and Level(assuming resolution/frame rate support too) + return ImmutableList.of( + H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO); + }; + MediaCodecVideoRenderer renderer = + new MediaCodecVideoRenderer( + ApplicationProvider.getApplicationContext(), + mediaCodecSelector, + /* allowedJoiningTimeMs= */ 0, + /* eventHandler= */ new Handler(testMainLooper), + /* eventListener= */ eventListener, + /* maxDroppedFramesToNotify= */ 1); + renderer.init(/* index= */ 0, PlayerId.UNSET); + + List mediaCodecInfoList = + renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false); + @Capabilities int capabilities = renderer.supportsFormat(avcFormat); + + assertThat(mediaCodecInfoList).hasSize(2); + assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isFalse(); + assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED); + assertThat(RendererCapabilities.getDecoderSupport(capabilities)) + .isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY); + } + + private static CodecCapabilities createCodecCapabilities(int profile, int level) { + CodecCapabilities capabilities = new CodecCapabilities(); + capabilities.profileLevels = new CodecProfileLevel[] {new CodecProfileLevel()}; + capabilities.profileLevels[0].profile = profile; + capabilities.profileLevels[0].level = level; + return capabilities; + } + @Test public void getCodecMaxInputSize_videoH263() { MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263); From e2e7c43ca0c783a634ad7f7f63d9d339142a0443 Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 16 Nov 2022 18:07:00 +0000 Subject: [PATCH 002/104] Fix NPE when listener is not set PiperOrigin-RevId: 488970696 (cherry picked from commit f52bb274b8b54df2404ab485c7d277150005b679) --- .../google/android/exoplayer2/audio/DefaultAudioSink.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 7653adf8da..f1380998f5 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 @@ -991,9 +991,11 @@ public final class DefaultAudioSink implements AudioSink { getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount()); if (!startMediaTimeUsNeedsSync && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { - listener.onAudioSinkError( - new AudioSink.UnexpectedDiscontinuityException( - presentationTimeUs, expectedPresentationTimeUs)); + if (listener != null) { + listener.onAudioSinkError( + new AudioSink.UnexpectedDiscontinuityException( + presentationTimeUs, expectedPresentationTimeUs)); + } startMediaTimeUsNeedsSync = true; } if (startMediaTimeUsNeedsSync) { From e42c65bc3ef8ddb0689bfb5ba2ab9534113782c8 Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 16 Nov 2022 18:42:27 +0000 Subject: [PATCH 003/104] Add setPlaybackLooper ExoPlayer builder method The method allows clients to specify a pre-existing thread to use for playback. This can be used to run multiple ExoPlayer instances on the same playback thread. PiperOrigin-RevId: 488980749 (cherry picked from commit 79b809b5563260cd20541afe607e36ae351fbbed) --- .../google/android/exoplayer2/ExoPlayer.java | 20 +++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 3 +- .../exoplayer2/ExoPlayerImplInternal.java | 33 ++++++++++++------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index a842e736e1..6889337910 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -24,6 +24,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioTrack; import android.media.MediaCodec; import android.os.Looper; +import android.os.Process; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -473,6 +474,7 @@ public interface ExoPlayer extends Player { /* package */ long detachSurfaceTimeoutMs; /* package */ boolean pauseAtEndOfMediaItems; /* package */ boolean usePlatformDiagnostics; + @Nullable /* package */ Looper playbackLooper; /* package */ boolean buildCalled; /** @@ -515,6 +517,7 @@ public interface ExoPlayer extends Player { *
  • {@code pauseAtEndOfMediaItems}: {@code false} *
  • {@code usePlatformDiagnostics}: {@code true} *
  • {@link Clock}: {@link Clock#DEFAULT} + *
  • {@code playbackLooper}: {@code null} (create new thread) * * * @param context A {@link Context}. @@ -1097,6 +1100,23 @@ public interface ExoPlayer extends Player { return this; } + /** + * Sets the {@link Looper} that will be used for playback. + * + *

    The backing thread should run with priority {@link Process#THREAD_PRIORITY_AUDIO} and + * should handle messages within 10ms. + * + * @param playbackLooper A {@link looper}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + @CanIgnoreReturnValue + public Builder setPlaybackLooper(Looper playbackLooper) { + checkState(!buildCalled); + this.playbackLooper = playbackLooper; + return this; + } + /** * Builds an {@link ExoPlayer} instance. * 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 0a91d7b693..da7e5eb31c 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 @@ -334,7 +334,8 @@ import java.util.concurrent.TimeoutException; applicationLooper, clock, playbackInfoUpdateListener, - playerId); + playerId, + builder.playbackLooper); volume = 1; repeatMode = Player.REPEAT_MODE_OFF; 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 bddba18093..7680e6a0a5 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 @@ -181,7 +181,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private final LoadControl loadControl; private final BandwidthMeter bandwidthMeter; private final HandlerWrapper handler; - private final HandlerThread internalPlaybackThread; + @Nullable private final HandlerThread internalPlaybackThread; private final Looper playbackLooper; private final Timeline.Window window; private final Timeline.Period period; @@ -236,7 +236,8 @@ import java.util.concurrent.atomic.AtomicBoolean; Looper applicationLooper, Clock clock, PlaybackInfoUpdateListener playbackInfoUpdateListener, - PlayerId playerId) { + PlayerId playerId, + Looper playbackLooper) { this.playbackInfoUpdateListener = playbackInfoUpdateListener; this.renderers = renderers; this.trackSelector = trackSelector; @@ -277,12 +278,18 @@ import java.util.concurrent.atomic.AtomicBoolean; mediaSourceList = new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId); - // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can - // not normally change to this priority" is incorrect. - internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO); - internalPlaybackThread.start(); - playbackLooper = internalPlaybackThread.getLooper(); - handler = clock.createHandler(playbackLooper, this); + if (playbackLooper != null) { + internalPlaybackThread = null; + this.playbackLooper = playbackLooper; + } else { + // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can + // not normally change to this priority" is incorrect. + internalPlaybackThread = + new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO); + internalPlaybackThread.start(); + this.playbackLooper = internalPlaybackThread.getLooper(); + } + handler = clock.createHandler(this.playbackLooper, this); } public void experimentalSetForegroundModeTimeoutMs(long setForegroundModeTimeoutMs) { @@ -385,7 +392,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @Override public synchronized void sendMessage(PlayerMessage message) { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { Log.w(TAG, "Ignoring messages sent after release."); message.markAsProcessed(/* isDelivered= */ false); return; @@ -400,7 +407,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @return Whether the operations succeeded. If false, the operation timed out. */ public synchronized boolean setForegroundMode(boolean foregroundMode) { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { return true; } if (foregroundMode) { @@ -422,7 +429,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @return Whether the release succeeded. If false, the release timed out. */ public synchronized boolean release() { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { return true; } handler.sendEmptyMessage(MSG_RELEASE); @@ -1374,7 +1381,9 @@ import java.util.concurrent.atomic.AtomicBoolean; /* resetError= */ false); loadControl.onReleased(); setState(Player.STATE_IDLE); - internalPlaybackThread.quit(); + if (internalPlaybackThread != null) { + internalPlaybackThread.quit(); + } synchronized (this) { released = true; notifyAll(); From 0d11d551111ef7825da621ec0ed530a336ab5736 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 17 Nov 2022 15:53:26 +0000 Subject: [PATCH 004/104] Mark broadcast receivers as not exported They are called from the system only and don't need to be exported to be visible to other apps. PiperOrigin-RevId: 489210264 (cherry picked from commit c1fd03df7403019143f503bbe208d73cabc11243) --- .../android/exoplayer2/ui/PlayerNotificationManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 316343d790..a129954bc4 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 @@ -1162,8 +1162,7 @@ public class PlayerNotificationManager { Notification notification = builder.build(); notificationManager.notify(notificationId, notification); if (!isNotificationStarted) { - // TODO(b/197817693): Explicitly indicate whether the receiver should be exported. - context.registerReceiver(notificationBroadcastReceiver, intentFilter); + Util.registerReceiverNotExported(context, notificationBroadcastReceiver, intentFilter); } if (notificationListener != null) { // Always pass true for ongoing with the first notification to tell a service to go into From d99a66774f8062ae615e03ce6df22e001194f688 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 17 Nov 2022 17:41:02 +0000 Subject: [PATCH 005/104] Throw exception if a released player is passed to TestPlayerRunHelper I considered moving this enforcement inside the ExoPlayerImpl implementation, but it might lead to app crashes in cases that apps (incorrectly) call a released player, but it wasn't actually causing a problem. PiperOrigin-RevId: 489233917 (cherry picked from commit d4c9199a6175a6c89f7a3d3900af1b9dfcb075f0) --- .../robolectric/TestPlayerRunHelper.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java index 85869c0750..aa878c3d4b 100644 --- a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java +++ b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.robolectric; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; import android.os.Looper; import com.google.android.exoplayer2.ExoPlaybackException; @@ -53,6 +54,9 @@ public class TestPlayerRunHelper { public static void runUntilPlaybackState(Player player, @Player.State int expectedState) throws TimeoutException { verifyMainTestThread(player); + if (player instanceof ExoPlayer) { + verifyPlaybackThreadIsAlive((ExoPlayer) player); + } runMainLooperUntil( () -> player.getPlaybackState() == expectedState || player.getPlayerError() != null); if (player.getPlayerError() != null) { @@ -74,6 +78,9 @@ public class TestPlayerRunHelper { public static void runUntilPlayWhenReady(Player player, boolean expectedPlayWhenReady) throws TimeoutException { verifyMainTestThread(player); + if (player instanceof ExoPlayer) { + verifyPlaybackThreadIsAlive((ExoPlayer) player); + } runMainLooperUntil( () -> player.getPlayWhenReady() == expectedPlayWhenReady || player.getPlayerError() != null); @@ -96,6 +103,9 @@ public class TestPlayerRunHelper { public static void runUntilTimelineChanged(Player player, Timeline expectedTimeline) throws TimeoutException { verifyMainTestThread(player); + if (player instanceof ExoPlayer) { + verifyPlaybackThreadIsAlive((ExoPlayer) player); + } runMainLooperUntil( () -> expectedTimeline.equals(player.getCurrentTimeline()) @@ -149,6 +159,9 @@ public class TestPlayerRunHelper { public static void runUntilPositionDiscontinuity( Player player, @Player.DiscontinuityReason int expectedReason) throws TimeoutException { verifyMainTestThread(player); + if (player instanceof ExoPlayer) { + verifyPlaybackThreadIsAlive((ExoPlayer) player); + } AtomicBoolean receivedCallback = new AtomicBoolean(false); Player.Listener listener = new Player.Listener() { @@ -178,6 +191,8 @@ public class TestPlayerRunHelper { */ public static ExoPlaybackException runUntilError(ExoPlayer player) throws TimeoutException { verifyMainTestThread(player); + verifyPlaybackThreadIsAlive(player); + runMainLooperUntil(() -> player.getPlayerError() != null); return checkNotNull(player.getPlayerError()); } @@ -197,6 +212,8 @@ public class TestPlayerRunHelper { public static void runUntilSleepingForOffload(ExoPlayer player, boolean expectedSleepForOffload) throws TimeoutException { verifyMainTestThread(player); + verifyPlaybackThreadIsAlive(player); + AtomicBoolean receiverCallback = new AtomicBoolean(false); ExoPlayer.AudioOffloadListener listener = new ExoPlayer.AudioOffloadListener() { @@ -226,6 +243,8 @@ public class TestPlayerRunHelper { */ public static void runUntilRenderedFirstFrame(ExoPlayer player) throws TimeoutException { verifyMainTestThread(player); + verifyPlaybackThreadIsAlive(player); + AtomicBoolean receivedCallback = new AtomicBoolean(false); Player.Listener listener = new Player.Listener() { @@ -257,6 +276,7 @@ public class TestPlayerRunHelper { public static void playUntilPosition(ExoPlayer player, int mediaItemIndex, long positionMs) throws TimeoutException { verifyMainTestThread(player); + verifyPlaybackThreadIsAlive(player); Looper applicationLooper = Util.getCurrentOrMainLooper(); AtomicBoolean messageHandled = new AtomicBoolean(false); player @@ -317,6 +337,8 @@ public class TestPlayerRunHelper { public static void runUntilPendingCommandsAreFullyHandled(ExoPlayer player) throws TimeoutException { verifyMainTestThread(player); + verifyPlaybackThreadIsAlive(player); + // Send message to player that will arrive after all other pending commands. Thus, the message // execution on the app thread will also happen after all other pending command // acknowledgements have arrived back on the app thread. @@ -334,4 +356,10 @@ public class TestPlayerRunHelper { throw new IllegalStateException(); } } + + private static void verifyPlaybackThreadIsAlive(ExoPlayer player) { + checkState( + player.getPlaybackLooper().getThread().isAlive(), + "Playback thread is not alive, has the player been released?"); + } } From be2bdbd05b35c6672be693625b9e4a7529f0f5d3 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 17 Nov 2022 17:52:17 +0000 Subject: [PATCH 006/104] Add additional codecs to the eosPropagationWorkaround list. Issue: google/ExoPlayer#10756 PiperOrigin-RevId: 489236336 (cherry picked from commit cbcdbfe021d9ffb453ed0e67a9ca1ea5f8691f51) --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 161166a1cf..264b31a9a9 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 @@ -2431,7 +2431,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { || (Util.SDK_INT <= 17 && "OMX.allwinner.video.decoder.avc".equals(name)) || (Util.SDK_INT <= 29 && ("OMX.broadcom.video_decoder.tunnel".equals(name) - || "OMX.broadcom.video_decoder.tunnel.secure".equals(name))) + || "OMX.broadcom.video_decoder.tunnel.secure".equals(name) + || "OMX.bcm.vdec.avc.tunnel".equals(name) + || "OMX.bcm.vdec.avc.tunnel.secure".equals(name) + || "OMX.bcm.vdec.hevc.tunnel".equals(name) + || "OMX.bcm.vdec.hevc.tunnel.secure".equals(name))) || ("Amazon".equals(Util.MANUFACTURER) && "AFTS".equals(Util.MODEL) && codecInfo.secure); } From fcf5452f19ab54ff180affbed622cf27cb6238bf Mon Sep 17 00:00:00 2001 From: christosts Date: Thu, 17 Nov 2022 18:00:55 +0000 Subject: [PATCH 007/104] Pass correct frame size for passthrough playback When estimating the AudioTrack min buffer size, we must use a PCM frame of 1 when doing direct playback (passthrough). The code was passing -1 (C.LENGTH_UNSET). PiperOrigin-RevId: 489238392 (cherry picked from commit d9d716869b7df2cd95704e9ac24a5f9a376afa2c) --- .../com/google/android/exoplayer2/audio/DefaultAudioSink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f1380998f5..bd9ee4d568 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 @@ -779,7 +779,7 @@ public final class DefaultAudioSink implements AudioSink { getAudioTrackMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding), outputEncoding, outputMode, - outputPcmFrameSize, + outputPcmFrameSize != C.LENGTH_UNSET ? outputPcmFrameSize : 1, outputSampleRate, enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED); From f1a9a6b549817ca96bb0414d981511a5a21406d1 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 18 Nov 2022 18:10:57 +0000 Subject: [PATCH 008/104] Add remaining state and getters to SimpleBasePlayer This adds the full Builders and State representation needed to implement all Player getter methods and listener invocations. PiperOrigin-RevId: 489503319 (cherry picked from commit b81cd08271cd94f244ca5d7c995cd29523057a60) --- .../android/exoplayer2/SimpleBasePlayer.java | 2332 ++++++++++++++++- .../exoplayer2/SimpleBasePlayerTest.java | 1576 ++++++++++- 2 files changed, 3813 insertions(+), 95 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 2437262315..a962b8220b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -15,14 +15,21 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.android.exoplayer2.util.Util.usToMs; +import static java.lang.Math.max; import android.os.Looper; +import android.os.SystemClock; +import android.util.Pair; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; +import androidx.annotation.FloatRange; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.text.CueGroup; @@ -34,10 +41,12 @@ import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; @@ -91,18 +100,138 @@ public abstract class SimpleBasePlayer extends BasePlayer { private Commands availableCommands; private boolean playWhenReady; private @PlayWhenReadyChangeReason int playWhenReadyChangeReason; + private @Player.State int playbackState; + private @PlaybackSuppressionReason int playbackSuppressionReason; + @Nullable private PlaybackException playerError; + private @RepeatMode int repeatMode; + private boolean shuffleModeEnabled; + private boolean isLoading; + private long seekBackIncrementMs; + private long seekForwardIncrementMs; + private long maxSeekToPreviousPositionMs; + private PlaybackParameters playbackParameters; + private TrackSelectionParameters trackSelectionParameters; + private AudioAttributes audioAttributes; + private float volume; + private VideoSize videoSize; + private CueGroup currentCues; + private DeviceInfo deviceInfo; + private int deviceVolume; + private boolean isDeviceMuted; + private int audioSessionId; + private boolean skipSilenceEnabled; + private Size surfaceSize; + private boolean newlyRenderedFirstFrame; + private Metadata timedMetadata; + private ImmutableList playlistItems; + private Timeline timeline; + private MediaMetadata playlistMetadata; + private int currentMediaItemIndex; + private int currentPeriodIndex; + private int currentAdGroupIndex; + private int currentAdIndexInAdGroup; + private long contentPositionMs; + private PositionSupplier contentPositionMsSupplier; + private long adPositionMs; + private PositionSupplier adPositionMsSupplier; + private PositionSupplier contentBufferedPositionMsSupplier; + private PositionSupplier adBufferedPositionMsSupplier; + private PositionSupplier totalBufferedDurationMsSupplier; + private boolean hasPositionDiscontinuity; + private @Player.DiscontinuityReason int positionDiscontinuityReason; + private long discontinuityPositionMs; /** Creates the builder. */ public Builder() { availableCommands = Commands.EMPTY; playWhenReady = false; playWhenReadyChangeReason = Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; + playbackState = Player.STATE_IDLE; + playbackSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE; + playerError = null; + repeatMode = Player.REPEAT_MODE_OFF; + shuffleModeEnabled = false; + isLoading = false; + seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS; + seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; + maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; + playbackParameters = PlaybackParameters.DEFAULT; + trackSelectionParameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT; + audioAttributes = AudioAttributes.DEFAULT; + volume = 1f; + videoSize = VideoSize.UNKNOWN; + currentCues = CueGroup.EMPTY_TIME_ZERO; + deviceInfo = DeviceInfo.UNKNOWN; + deviceVolume = 0; + isDeviceMuted = false; + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + skipSilenceEnabled = false; + surfaceSize = Size.UNKNOWN; + newlyRenderedFirstFrame = false; + timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET); + playlistItems = ImmutableList.of(); + timeline = Timeline.EMPTY; + playlistMetadata = MediaMetadata.EMPTY; + currentMediaItemIndex = 0; + currentPeriodIndex = C.INDEX_UNSET; + currentAdGroupIndex = C.INDEX_UNSET; + currentAdIndexInAdGroup = C.INDEX_UNSET; + contentPositionMs = C.TIME_UNSET; + contentPositionMsSupplier = PositionSupplier.ZERO; + adPositionMs = C.TIME_UNSET; + adPositionMsSupplier = PositionSupplier.ZERO; + contentBufferedPositionMsSupplier = PositionSupplier.ZERO; + adBufferedPositionMsSupplier = PositionSupplier.ZERO; + totalBufferedDurationMsSupplier = PositionSupplier.ZERO; + hasPositionDiscontinuity = false; + positionDiscontinuityReason = Player.DISCONTINUITY_REASON_INTERNAL; + discontinuityPositionMs = 0; } private Builder(State state) { this.availableCommands = state.availableCommands; this.playWhenReady = state.playWhenReady; this.playWhenReadyChangeReason = state.playWhenReadyChangeReason; + this.playbackState = state.playbackState; + this.playbackSuppressionReason = state.playbackSuppressionReason; + this.playerError = state.playerError; + this.repeatMode = state.repeatMode; + this.shuffleModeEnabled = state.shuffleModeEnabled; + this.isLoading = state.isLoading; + this.seekBackIncrementMs = state.seekBackIncrementMs; + this.seekForwardIncrementMs = state.seekForwardIncrementMs; + this.maxSeekToPreviousPositionMs = state.maxSeekToPreviousPositionMs; + this.playbackParameters = state.playbackParameters; + this.trackSelectionParameters = state.trackSelectionParameters; + this.audioAttributes = state.audioAttributes; + this.volume = state.volume; + this.videoSize = state.videoSize; + this.currentCues = state.currentCues; + this.deviceInfo = state.deviceInfo; + this.deviceVolume = state.deviceVolume; + this.isDeviceMuted = state.isDeviceMuted; + this.audioSessionId = state.audioSessionId; + this.skipSilenceEnabled = state.skipSilenceEnabled; + this.surfaceSize = state.surfaceSize; + this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame; + this.timedMetadata = state.timedMetadata; + this.playlistItems = state.playlistItems; + this.timeline = state.timeline; + this.playlistMetadata = state.playlistMetadata; + this.currentMediaItemIndex = state.currentMediaItemIndex; + this.currentPeriodIndex = state.currentPeriodIndex; + this.currentAdGroupIndex = state.currentAdGroupIndex; + this.currentAdIndexInAdGroup = state.currentAdIndexInAdGroup; + this.contentPositionMs = C.TIME_UNSET; + this.contentPositionMsSupplier = state.contentPositionMsSupplier; + this.adPositionMs = C.TIME_UNSET; + this.adPositionMsSupplier = state.adPositionMsSupplier; + this.contentBufferedPositionMsSupplier = state.contentBufferedPositionMsSupplier; + this.adBufferedPositionMsSupplier = state.adBufferedPositionMsSupplier; + this.totalBufferedDurationMsSupplier = state.totalBufferedDurationMsSupplier; + this.hasPositionDiscontinuity = state.hasPositionDiscontinuity; + this.positionDiscontinuityReason = state.positionDiscontinuityReason; + this.discontinuityPositionMs = state.discontinuityPositionMs; } /** @@ -133,6 +262,542 @@ public abstract class SimpleBasePlayer extends BasePlayer { return this; } + /** + * Sets the {@linkplain Player.State state} of the player. + * + *

    If the {@linkplain #setPlaylist playlist} is empty, the state must be either {@link + * Player#STATE_IDLE} or {@link Player#STATE_ENDED}. + * + * @param playbackState The {@linkplain Player.State state} of the player. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlaybackState(@Player.State int playbackState) { + this.playbackState = playbackState; + return this; + } + + /** + * Sets the reason why playback is suppressed even if {@link #getPlayWhenReady()} is true. + * + * @param playbackSuppressionReason The {@link Player.PlaybackSuppressionReason} why playback + * is suppressed even if {@link #getPlayWhenReady()} is true. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlaybackSuppressionReason( + @Player.PlaybackSuppressionReason int playbackSuppressionReason) { + this.playbackSuppressionReason = playbackSuppressionReason; + return this; + } + + /** + * Sets last error that caused playback to fail, or null if there was no error. + * + *

    The {@linkplain #setPlaybackState playback state} must be set to {@link + * Player#STATE_IDLE} while an error is set. + * + * @param playerError The last error that caused playback to fail, or null if there was no + * error. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlayerError(@Nullable PlaybackException playerError) { + this.playerError = playerError; + return this; + } + + /** + * Sets the {@link RepeatMode} used for playback. + * + * @param repeatMode The {@link RepeatMode} used for playback. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setRepeatMode(@Player.RepeatMode int repeatMode) { + this.repeatMode = repeatMode; + return this; + } + + /** + * Sets whether shuffling of media items is enabled. + * + * @param shuffleModeEnabled Whether shuffling of media items is enabled. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setShuffleModeEnabled(boolean shuffleModeEnabled) { + this.shuffleModeEnabled = shuffleModeEnabled; + return this; + } + + /** + * Sets whether the player is currently loading its source. + * + *

    The player can not be marked as loading if the {@linkplain #setPlaybackState state} is + * {@link Player#STATE_IDLE} or {@link Player#STATE_ENDED}. + * + * @param isLoading Whether the player is currently loading its source. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsLoading(boolean isLoading) { + this.isLoading = isLoading; + return this; + } + + /** + * Sets the {@link Player#seekBack()} increment in milliseconds. + * + * @param seekBackIncrementMs The {@link Player#seekBack()} increment in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setSeekBackIncrementMs(long seekBackIncrementMs) { + this.seekBackIncrementMs = seekBackIncrementMs; + return this; + } + + /** + * Sets the {@link Player#seekForward()} increment in milliseconds. + * + * @param seekForwardIncrementMs The {@link Player#seekForward()} increment in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setSeekForwardIncrementMs(long seekForwardIncrementMs) { + this.seekForwardIncrementMs = seekForwardIncrementMs; + return this; + } + + /** + * Sets the maximum position for which {@link #seekToPrevious()} seeks to the previous item, + * in milliseconds. + * + * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} + * seeks to the previous item, in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setMaxSeekToPreviousPositionMs(long maxSeekToPreviousPositionMs) { + this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; + return this; + } + + /** + * Sets the currently active {@link PlaybackParameters}. + * + * @param playbackParameters The currently active {@link PlaybackParameters}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlaybackParameters(PlaybackParameters playbackParameters) { + this.playbackParameters = playbackParameters; + return this; + } + + /** + * Sets the currently active {@link TrackSelectionParameters}. + * + * @param trackSelectionParameters The currently active {@link TrackSelectionParameters}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setTrackSelectionParameters( + TrackSelectionParameters trackSelectionParameters) { + this.trackSelectionParameters = trackSelectionParameters; + return this; + } + + /** + * Sets the current {@link AudioAttributes}. + * + * @param audioAttributes The current {@link AudioAttributes}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAudioAttributes(AudioAttributes audioAttributes) { + this.audioAttributes = audioAttributes; + return this; + } + + /** + * Sets the current audio volume, with 0 being silence and 1 being unity gain (signal + * unchanged). + * + * @param volume The current audio volume, with 0 being silence and 1 being unity gain (signal + * unchanged). + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setVolume(@FloatRange(from = 0, to = 1.0) float volume) { + checkArgument(volume >= 0.0f && volume <= 1.0f); + this.volume = volume; + return this; + } + + /** + * Sets the current video size. + * + * @param videoSize The current video size. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setVideoSize(VideoSize videoSize) { + this.videoSize = videoSize; + return this; + } + + /** + * Sets the current {@linkplain CueGroup cues}. + * + * @param currentCues The current {@linkplain CueGroup cues}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setCurrentCues(CueGroup currentCues) { + this.currentCues = currentCues; + return this; + } + + /** + * Sets the {@link DeviceInfo}. + * + * @param deviceInfo The {@link DeviceInfo}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setDeviceInfo(DeviceInfo deviceInfo) { + this.deviceInfo = deviceInfo; + return this; + } + + /** + * Sets the current device volume. + * + * @param deviceVolume The current device volume. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setDeviceVolume(@IntRange(from = 0) int deviceVolume) { + checkArgument(deviceVolume >= 0); + this.deviceVolume = deviceVolume; + return this; + } + + /** + * Sets whether the device is muted. + * + * @param isDeviceMuted Whether the device is muted. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsDeviceMuted(boolean isDeviceMuted) { + this.isDeviceMuted = isDeviceMuted; + return this; + } + + /** + * Sets the audio session id. + * + * @param audioSessionId The audio session id. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAudioSessionId(int audioSessionId) { + this.audioSessionId = audioSessionId; + return this; + } + + /** + * Sets whether skipping silences in the audio stream is enabled. + * + * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setSkipSilenceEnabled(boolean skipSilenceEnabled) { + this.skipSilenceEnabled = skipSilenceEnabled; + return this; + } + + /** + * Sets the size of the surface onto which the video is being rendered. + * + * @param surfaceSize The surface size. Dimensions may be {@link C#LENGTH_UNSET} if unknown, + * or 0 if the video is not rendered onto a surface. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setSurfaceSize(Size surfaceSize) { + this.surfaceSize = surfaceSize; + return this; + } + + /** + * Sets whether a frame has been rendered for the first time since setting the surface, a + * rendering reset, or since the stream being rendered was changed. + * + *

    Note: As this will trigger a {@link Listener#onRenderedFirstFrame()} event, the flag + * should only be set for the first {@link State} update after the first frame was rendered. + * + * @param newlyRenderedFirstFrame Whether the first frame was newly rendered. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setNewlyRenderedFirstFrame(boolean newlyRenderedFirstFrame) { + this.newlyRenderedFirstFrame = newlyRenderedFirstFrame; + return this; + } + + /** + * Sets the most recent timed {@link Metadata}. + * + *

    Metadata with a {@link Metadata#presentationTimeUs} of {@link C#TIME_UNSET} will not be + * forwarded to listeners. + * + * @param timedMetadata The most recent timed {@link Metadata}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setTimedMetadata(Metadata timedMetadata) { + this.timedMetadata = timedMetadata; + return this; + } + + /** + * Sets the playlist items. + * + *

    All playlist items must have unique {@linkplain PlaylistItem.Builder#setUid UIDs}. + * + * @param playlistItems The list of playlist items. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlaylist(List playlistItems) { + HashSet uids = new HashSet<>(); + for (int i = 0; i < playlistItems.size(); i++) { + checkArgument(uids.add(playlistItems.get(i).uid)); + } + this.playlistItems = ImmutableList.copyOf(playlistItems); + this.timeline = new PlaylistTimeline(this.playlistItems); + return this; + } + + /** + * Sets the playlist {@link MediaMetadata}. + * + * @param playlistMetadata The playlist {@link MediaMetadata}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPlaylistMetadata(MediaMetadata playlistMetadata) { + this.playlistMetadata = playlistMetadata; + return this; + } + + /** + * Sets the current media item index. + * + *

    The media item index must be less than the number of {@linkplain #setPlaylist playlist + * items}, if set. + * + * @param currentMediaItemIndex The current media item index. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setCurrentMediaItemIndex(int currentMediaItemIndex) { + this.currentMediaItemIndex = currentMediaItemIndex; + return this; + } + + /** + * Sets the current period index, or {@link C#INDEX_UNSET} to assume the first period of the + * current playlist item is played. + * + *

    The period index must be less than the total number of {@linkplain + * PlaylistItem.Builder#setPeriods periods} in the playlist, if set, and the period at the + * specified index must be part of the {@linkplain #setCurrentMediaItemIndex current playlist + * item}. + * + * @param currentPeriodIndex The current period index, or {@link C#INDEX_UNSET} to assume the + * first period of the current playlist item is played. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setCurrentPeriodIndex(int currentPeriodIndex) { + checkArgument(currentPeriodIndex == C.INDEX_UNSET || currentPeriodIndex >= 0); + this.currentPeriodIndex = currentPeriodIndex; + return this; + } + + /** + * Sets the current ad indices, or {@link C#INDEX_UNSET} if no ad is playing. + * + *

    Either both indices need to be {@link C#INDEX_UNSET} or both are not {@link + * C#INDEX_UNSET}. + * + *

    Ads indices can only be set if there is a corresponding {@link AdPlaybackState} defined + * in the current {@linkplain PlaylistItem.Builder#setPeriods period}. + * + * @param adGroupIndex The current ad group index, or {@link C#INDEX_UNSET} if no ad is + * playing. + * @param adIndexInAdGroup The current ad index in the ad group, or {@link C#INDEX_UNSET} if + * no ad is playing. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setCurrentAd(int adGroupIndex, int adIndexInAdGroup) { + checkArgument((adGroupIndex == C.INDEX_UNSET) == (adIndexInAdGroup == C.INDEX_UNSET)); + this.currentAdGroupIndex = adGroupIndex; + this.currentAdIndexInAdGroup = adIndexInAdGroup; + return this; + } + + /** + * Sets the current content playback position in milliseconds. + * + *

    This position will be converted to an advancing {@link PositionSupplier} if the overall + * state indicates an advancing playback position. + * + * @param positionMs The current content playback position in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setContentPositionMs(long positionMs) { + this.contentPositionMs = positionMs; + return this; + } + + /** + * Sets the {@link PositionSupplier} for the current content playback position in + * milliseconds. + * + *

    The supplier is expected to return the updated position on every call if the playback is + * advancing, for example by using {@link PositionSupplier#getExtrapolating}. + * + * @param contentPositionMsSupplier The {@link PositionSupplier} for the current content + * playback position in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setContentPositionMs(PositionSupplier contentPositionMsSupplier) { + this.contentPositionMs = C.TIME_UNSET; + this.contentPositionMsSupplier = contentPositionMsSupplier; + return this; + } + + /** + * Sets the current ad playback position in milliseconds. The * value is unused if no ad is + * playing. + * + *

    This position will be converted to an advancing {@link PositionSupplier} if the overall + * state indicates an advancing ad playback position. + * + * @param positionMs The current ad playback position in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAdPositionMs(long positionMs) { + this.adPositionMs = positionMs; + return this; + } + + /** + * Sets the {@link PositionSupplier} for the current ad playback position in milliseconds. The + * value is unused if no ad is playing. + * + *

    The supplier is expected to return the updated position on every call if the playback is + * advancing, for example by using {@link PositionSupplier#getExtrapolating}. + * + * @param adPositionMsSupplier The {@link PositionSupplier} for the current ad playback + * position in milliseconds. The value is unused if no ad is playing. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAdPositionMs(PositionSupplier adPositionMsSupplier) { + this.adPositionMs = C.TIME_UNSET; + this.adPositionMsSupplier = adPositionMsSupplier; + return this; + } + + /** + * Sets the {@link PositionSupplier} for the estimated position up to which the currently + * playing content is buffered, in milliseconds. + * + * @param contentBufferedPositionMsSupplier The {@link PositionSupplier} for the estimated + * position up to which the currently playing content is buffered, in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setContentBufferedPositionMs( + PositionSupplier contentBufferedPositionMsSupplier) { + this.contentBufferedPositionMsSupplier = contentBufferedPositionMsSupplier; + return this; + } + + /** + * Sets the {@link PositionSupplier} for the estimated position up to which the currently + * playing ad is buffered, in milliseconds. The value is unused if no ad is playing. + * + * @param adBufferedPositionMsSupplier The {@link PositionSupplier} for the estimated position + * up to which the currently playing ad is buffered, in milliseconds. The value is unused + * if no ad is playing. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAdBufferedPositionMs(PositionSupplier adBufferedPositionMsSupplier) { + this.adBufferedPositionMsSupplier = adBufferedPositionMsSupplier; + return this; + } + + /** + * Sets the {@link PositionSupplier} for the estimated total buffered duration in + * milliseconds. + * + * @param totalBufferedDurationMsSupplier The {@link PositionSupplier} for the estimated total + * buffered duration in milliseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setTotalBufferedDurationMs(PositionSupplier totalBufferedDurationMsSupplier) { + this.totalBufferedDurationMsSupplier = totalBufferedDurationMsSupplier; + return this; + } + + /** + * Signals that a position discontinuity happened since the last player update and sets the + * reason for it. + * + * @param positionDiscontinuityReason The {@linkplain Player.DiscontinuityReason reason} for + * the discontinuity. + * @param discontinuityPositionMs The position, in milliseconds, in the current content or ad + * from which playback continues after the discontinuity. + * @return This builder. + * @see #clearPositionDiscontinuity + */ + @CanIgnoreReturnValue + public Builder setPositionDiscontinuity( + @Player.DiscontinuityReason int positionDiscontinuityReason, + long discontinuityPositionMs) { + this.hasPositionDiscontinuity = true; + this.positionDiscontinuityReason = positionDiscontinuityReason; + this.discontinuityPositionMs = discontinuityPositionMs; + return this; + } + + /** + * Clears a previously set position discontinuity signal. + * + * @return This builder. + * @see #hasPositionDiscontinuity + */ + @CanIgnoreReturnValue + public Builder clearPositionDiscontinuity() { + this.hasPositionDiscontinuity = false; + return this; + } + /** Builds the {@link State}. */ public State build() { return new State(this); @@ -145,11 +810,211 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final boolean playWhenReady; /** The last reason for changing {@link #playWhenReady}. */ public final @PlayWhenReadyChangeReason int playWhenReadyChangeReason; + /** The {@linkplain Player.State state} of the player. */ + public final @Player.State int playbackState; + /** The reason why playback is suppressed even if {@link #getPlayWhenReady()} is true. */ + public final @PlaybackSuppressionReason int playbackSuppressionReason; + /** The last error that caused playback to fail, or null if there was no error. */ + @Nullable public final PlaybackException playerError; + /** The {@link RepeatMode} used for playback. */ + public final @RepeatMode int repeatMode; + /** Whether shuffling of media items is enabled. */ + public final boolean shuffleModeEnabled; + /** Whether the player is currently loading its source. */ + public final boolean isLoading; + /** The {@link Player#seekBack()} increment in milliseconds. */ + public final long seekBackIncrementMs; + /** The {@link Player#seekForward()} increment in milliseconds. */ + public final long seekForwardIncrementMs; + /** + * The maximum position for which {@link #seekToPrevious()} seeks to the previous item, in + * milliseconds. + */ + public final long maxSeekToPreviousPositionMs; + /** The currently active {@link PlaybackParameters}. */ + public final PlaybackParameters playbackParameters; + /** The currently active {@link TrackSelectionParameters}. */ + public final TrackSelectionParameters trackSelectionParameters; + /** The current {@link AudioAttributes}. */ + public final AudioAttributes audioAttributes; + /** The current audio volume, with 0 being silence and 1 being unity gain (signal unchanged). */ + @FloatRange(from = 0, to = 1.0) + public final float volume; + /** The current video size. */ + public final VideoSize videoSize; + /** The current {@linkplain CueGroup cues}. */ + public final CueGroup currentCues; + /** The {@link DeviceInfo}. */ + public final DeviceInfo deviceInfo; + /** The current device volume. */ + @IntRange(from = 0) + public final int deviceVolume; + /** Whether the device is muted. */ + public final boolean isDeviceMuted; + /** The audio session id. */ + public final int audioSessionId; + /** Whether skipping silences in the audio stream is enabled. */ + public final boolean skipSilenceEnabled; + /** The size of the surface onto which the video is being rendered. */ + public final Size surfaceSize; + /** + * Whether a frame has been rendered for the first time since setting the surface, a rendering + * reset, or since the stream being rendered was changed. + */ + public final boolean newlyRenderedFirstFrame; + /** The most recent timed metadata. */ + public final Metadata timedMetadata; + /** The playlist items. */ + public final ImmutableList playlistItems; + /** The {@link Timeline} derived from the {@linkplain #playlistItems playlist items}. */ + public final Timeline timeline; + /** The playlist {@link MediaMetadata}. */ + public final MediaMetadata playlistMetadata; + /** The current media item index. */ + public final int currentMediaItemIndex; + /** + * The current period index, or {@link C#INDEX_UNSET} to assume the first period of the current + * playlist item is played. + */ + public final int currentPeriodIndex; + /** The current ad group index, or {@link C#INDEX_UNSET} if no ad is playing. */ + public final int currentAdGroupIndex; + /** The current ad index in the ad group, or {@link C#INDEX_UNSET} if no ad is playing. */ + public final int currentAdIndexInAdGroup; + /** The {@link PositionSupplier} for the current content playback position in milliseconds. */ + public final PositionSupplier contentPositionMsSupplier; + /** + * The {@link PositionSupplier} for the current ad playback position in milliseconds. The value + * is unused if no ad is playing. + */ + public final PositionSupplier adPositionMsSupplier; + /** + * The {@link PositionSupplier} for the estimated position up to which the currently playing + * content is buffered, in milliseconds. + */ + public final PositionSupplier contentBufferedPositionMsSupplier; + /** + * The {@link PositionSupplier} for the estimated position up to which the currently playing ad + * is buffered, in milliseconds. The value is unused if no ad is playing. + */ + public final PositionSupplier adBufferedPositionMsSupplier; + /** The {@link PositionSupplier} for the estimated total buffered duration in milliseconds. */ + public final PositionSupplier totalBufferedDurationMsSupplier; + /** Signals that a position discontinuity happened since the last update to the player. */ + public final boolean hasPositionDiscontinuity; + /** + * The {@linkplain Player.DiscontinuityReason reason} for the last position discontinuity. The + * value is unused if {@link #hasPositionDiscontinuity} is {@code false}. + */ + public final @Player.DiscontinuityReason int positionDiscontinuityReason; + /** + * The position, in milliseconds, in the current content or ad from which playback continued + * after the discontinuity. The value is unused if {@link #hasPositionDiscontinuity} is {@code + * false}. + */ + public final long discontinuityPositionMs; private State(Builder builder) { + if (builder.timeline.isEmpty()) { + checkArgument( + builder.playbackState == Player.STATE_IDLE + || builder.playbackState == Player.STATE_ENDED); + } else { + checkArgument(builder.currentMediaItemIndex < builder.timeline.getWindowCount()); + if (builder.currentPeriodIndex != C.INDEX_UNSET) { + checkArgument(builder.currentPeriodIndex < builder.timeline.getPeriodCount()); + checkArgument( + builder.timeline.getPeriod(builder.currentPeriodIndex, new Timeline.Period()) + .windowIndex + == builder.currentMediaItemIndex); + } + if (builder.currentAdGroupIndex != C.INDEX_UNSET) { + int periodIndex = + builder.currentPeriodIndex != C.INDEX_UNSET + ? builder.currentPeriodIndex + : builder.timeline.getWindow(builder.currentMediaItemIndex, new Timeline.Window()) + .firstPeriodIndex; + Timeline.Period period = builder.timeline.getPeriod(periodIndex, new Timeline.Period()); + checkArgument(builder.currentAdGroupIndex < period.getAdGroupCount()); + int adCountInGroup = period.getAdCountInAdGroup(builder.currentAdGroupIndex); + if (adCountInGroup != C.LENGTH_UNSET) { + checkArgument(builder.currentAdIndexInAdGroup < adCountInGroup); + } + } + } + if (builder.playerError != null) { + checkArgument(builder.playbackState == Player.STATE_IDLE); + } + if (builder.playbackState == Player.STATE_IDLE + || builder.playbackState == Player.STATE_ENDED) { + checkArgument(!builder.isLoading); + } + PositionSupplier contentPositionMsSupplier = builder.contentPositionMsSupplier; + if (builder.contentPositionMs != C.TIME_UNSET) { + if (builder.currentAdGroupIndex == C.INDEX_UNSET + && builder.playWhenReady + && builder.playbackState == Player.STATE_READY + && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE) { + contentPositionMsSupplier = + PositionSupplier.getExtrapolating( + builder.contentPositionMs, builder.playbackParameters.speed); + } else { + contentPositionMsSupplier = PositionSupplier.getConstant(builder.contentPositionMs); + } + } + PositionSupplier adPositionMsSupplier = builder.adPositionMsSupplier; + if (builder.adPositionMs != C.TIME_UNSET) { + if (builder.currentAdGroupIndex != C.INDEX_UNSET + && builder.playWhenReady + && builder.playbackState == Player.STATE_READY + && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE) { + adPositionMsSupplier = + PositionSupplier.getExtrapolating(builder.adPositionMs, /* playbackSpeed= */ 1f); + } else { + adPositionMsSupplier = PositionSupplier.getConstant(builder.adPositionMs); + } + } this.availableCommands = builder.availableCommands; this.playWhenReady = builder.playWhenReady; this.playWhenReadyChangeReason = builder.playWhenReadyChangeReason; + this.playbackState = builder.playbackState; + this.playbackSuppressionReason = builder.playbackSuppressionReason; + this.playerError = builder.playerError; + this.repeatMode = builder.repeatMode; + this.shuffleModeEnabled = builder.shuffleModeEnabled; + this.isLoading = builder.isLoading; + this.seekBackIncrementMs = builder.seekBackIncrementMs; + this.seekForwardIncrementMs = builder.seekForwardIncrementMs; + this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs; + this.playbackParameters = builder.playbackParameters; + this.trackSelectionParameters = builder.trackSelectionParameters; + this.audioAttributes = builder.audioAttributes; + this.volume = builder.volume; + this.videoSize = builder.videoSize; + this.currentCues = builder.currentCues; + this.deviceInfo = builder.deviceInfo; + this.deviceVolume = builder.deviceVolume; + this.isDeviceMuted = builder.isDeviceMuted; + this.audioSessionId = builder.audioSessionId; + this.skipSilenceEnabled = builder.skipSilenceEnabled; + this.surfaceSize = builder.surfaceSize; + this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame; + this.timedMetadata = builder.timedMetadata; + this.playlistItems = builder.playlistItems; + this.timeline = builder.timeline; + this.playlistMetadata = builder.playlistMetadata; + this.currentMediaItemIndex = builder.currentMediaItemIndex; + this.currentPeriodIndex = builder.currentPeriodIndex; + this.currentAdGroupIndex = builder.currentAdGroupIndex; + this.currentAdIndexInAdGroup = builder.currentAdIndexInAdGroup; + this.contentPositionMsSupplier = contentPositionMsSupplier; + this.adPositionMsSupplier = adPositionMsSupplier; + this.contentBufferedPositionMsSupplier = builder.contentBufferedPositionMsSupplier; + this.adBufferedPositionMsSupplier = builder.adBufferedPositionMsSupplier; + this.totalBufferedDurationMsSupplier = builder.totalBufferedDurationMsSupplier; + this.hasPositionDiscontinuity = builder.hasPositionDiscontinuity; + this.positionDiscontinuityReason = builder.positionDiscontinuityReason; + this.discontinuityPositionMs = builder.discontinuityPositionMs; } /** Returns a {@link Builder} pre-populated with the current state values. */ @@ -168,7 +1033,44 @@ public abstract class SimpleBasePlayer extends BasePlayer { State state = (State) o; return playWhenReady == state.playWhenReady && playWhenReadyChangeReason == state.playWhenReadyChangeReason - && availableCommands.equals(state.availableCommands); + && availableCommands.equals(state.availableCommands) + && playbackState == state.playbackState + && playbackSuppressionReason == state.playbackSuppressionReason + && Util.areEqual(playerError, state.playerError) + && repeatMode == state.repeatMode + && shuffleModeEnabled == state.shuffleModeEnabled + && isLoading == state.isLoading + && seekBackIncrementMs == state.seekBackIncrementMs + && seekForwardIncrementMs == state.seekForwardIncrementMs + && maxSeekToPreviousPositionMs == state.maxSeekToPreviousPositionMs + && playbackParameters.equals(state.playbackParameters) + && trackSelectionParameters.equals(state.trackSelectionParameters) + && audioAttributes.equals(state.audioAttributes) + && volume == state.volume + && videoSize.equals(state.videoSize) + && currentCues.equals(state.currentCues) + && deviceInfo.equals(state.deviceInfo) + && deviceVolume == state.deviceVolume + && isDeviceMuted == state.isDeviceMuted + && audioSessionId == state.audioSessionId + && skipSilenceEnabled == state.skipSilenceEnabled + && surfaceSize.equals(state.surfaceSize) + && newlyRenderedFirstFrame == state.newlyRenderedFirstFrame + && timedMetadata.equals(state.timedMetadata) + && playlistItems.equals(state.playlistItems) + && playlistMetadata.equals(state.playlistMetadata) + && currentMediaItemIndex == state.currentMediaItemIndex + && currentPeriodIndex == state.currentPeriodIndex + && currentAdGroupIndex == state.currentAdGroupIndex + && currentAdIndexInAdGroup == state.currentAdIndexInAdGroup + && contentPositionMsSupplier.equals(state.contentPositionMsSupplier) + && adPositionMsSupplier.equals(state.adPositionMsSupplier) + && contentBufferedPositionMsSupplier.equals(state.contentBufferedPositionMsSupplier) + && adBufferedPositionMsSupplier.equals(state.adBufferedPositionMsSupplier) + && totalBufferedDurationMsSupplier.equals(state.totalBufferedDurationMsSupplier) + && hasPositionDiscontinuity == state.hasPositionDiscontinuity + && positionDiscontinuityReason == state.positionDiscontinuityReason + && discontinuityPositionMs == state.discontinuityPositionMs; } @Override @@ -177,14 +1079,903 @@ public abstract class SimpleBasePlayer extends BasePlayer { result = 31 * result + availableCommands.hashCode(); result = 31 * result + (playWhenReady ? 1 : 0); result = 31 * result + playWhenReadyChangeReason; + result = 31 * result + playbackState; + result = 31 * result + playbackSuppressionReason; + result = 31 * result + (playerError == null ? 0 : playerError.hashCode()); + result = 31 * result + repeatMode; + result = 31 * result + (shuffleModeEnabled ? 1 : 0); + result = 31 * result + (isLoading ? 1 : 0); + result = 31 * result + (int) (seekBackIncrementMs ^ (seekBackIncrementMs >>> 32)); + result = 31 * result + (int) (seekForwardIncrementMs ^ (seekForwardIncrementMs >>> 32)); + result = + 31 * result + (int) (maxSeekToPreviousPositionMs ^ (maxSeekToPreviousPositionMs >>> 32)); + result = 31 * result + playbackParameters.hashCode(); + result = 31 * result + trackSelectionParameters.hashCode(); + result = 31 * result + audioAttributes.hashCode(); + result = 31 * result + Float.floatToRawIntBits(volume); + result = 31 * result + videoSize.hashCode(); + result = 31 * result + currentCues.hashCode(); + result = 31 * result + deviceInfo.hashCode(); + result = 31 * result + deviceVolume; + result = 31 * result + (isDeviceMuted ? 1 : 0); + result = 31 * result + audioSessionId; + result = 31 * result + (skipSilenceEnabled ? 1 : 0); + result = 31 * result + surfaceSize.hashCode(); + result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0); + result = 31 * result + timedMetadata.hashCode(); + result = 31 * result + playlistItems.hashCode(); + result = 31 * result + playlistMetadata.hashCode(); + result = 31 * result + currentMediaItemIndex; + result = 31 * result + currentPeriodIndex; + result = 31 * result + currentAdGroupIndex; + result = 31 * result + currentAdIndexInAdGroup; + result = 31 * result + contentPositionMsSupplier.hashCode(); + result = 31 * result + adPositionMsSupplier.hashCode(); + result = 31 * result + contentBufferedPositionMsSupplier.hashCode(); + result = 31 * result + adBufferedPositionMsSupplier.hashCode(); + result = 31 * result + totalBufferedDurationMsSupplier.hashCode(); + result = 31 * result + (hasPositionDiscontinuity ? 1 : 0); + result = 31 * result + positionDiscontinuityReason; + result = 31 * result + (int) (discontinuityPositionMs ^ (discontinuityPositionMs >>> 32)); return result; } } + private static final class PlaylistTimeline extends Timeline { + + private final ImmutableList playlistItems; + private final int[] firstPeriodIndexByWindowIndex; + private final int[] windowIndexByPeriodIndex; + private final HashMap periodIndexByUid; + + public PlaylistTimeline(ImmutableList playlistItems) { + int playlistItemCount = playlistItems.size(); + this.playlistItems = playlistItems; + this.firstPeriodIndexByWindowIndex = new int[playlistItemCount]; + int periodCount = 0; + for (int i = 0; i < playlistItemCount; i++) { + PlaylistItem playlistItem = playlistItems.get(i); + firstPeriodIndexByWindowIndex[i] = periodCount; + periodCount += getPeriodCountInPlaylistItem(playlistItem); + } + this.windowIndexByPeriodIndex = new int[periodCount]; + this.periodIndexByUid = new HashMap<>(); + int periodIndex = 0; + for (int i = 0; i < playlistItemCount; i++) { + PlaylistItem playlistItem = playlistItems.get(i); + for (int j = 0; j < getPeriodCountInPlaylistItem(playlistItem); j++) { + periodIndexByUid.put(playlistItem.getPeriodUid(j), periodIndex); + windowIndexByPeriodIndex[periodIndex] = i; + periodIndex++; + } + } + } + + @Override + public int getWindowCount() { + return playlistItems.size(); + } + + @Override + public int getNextWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) { + // TODO: Support shuffle order. + return super.getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); + } + + @Override + public int getPreviousWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) { + // TODO: Support shuffle order. + return super.getPreviousWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); + } + + @Override + public int getLastWindowIndex(boolean shuffleModeEnabled) { + // TODO: Support shuffle order. + return super.getLastWindowIndex(shuffleModeEnabled); + } + + @Override + public int getFirstWindowIndex(boolean shuffleModeEnabled) { + // TODO: Support shuffle order. + return super.getFirstWindowIndex(shuffleModeEnabled); + } + + @Override + public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { + return playlistItems + .get(windowIndex) + .getWindow(firstPeriodIndexByWindowIndex[windowIndex], window); + } + + @Override + public int getPeriodCount() { + return windowIndexByPeriodIndex.length; + } + + @Override + public Period getPeriodByUid(Object periodUid, Period period) { + int periodIndex = checkNotNull(periodIndexByUid.get(periodUid)); + return getPeriod(periodIndex, period, /* setIds= */ true); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + int windowIndex = windowIndexByPeriodIndex[periodIndex]; + int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; + return playlistItems.get(windowIndex).getPeriod(windowIndex, periodIndexInWindow, period); + } + + @Override + public int getIndexOfPeriod(Object uid) { + @Nullable Integer index = periodIndexByUid.get(uid); + return index == null ? C.INDEX_UNSET : index; + } + + @Override + public Object getUidOfPeriod(int periodIndex) { + int windowIndex = windowIndexByPeriodIndex[periodIndex]; + int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; + return playlistItems.get(windowIndex).getPeriodUid(periodIndexInWindow); + } + + private static int getPeriodCountInPlaylistItem(PlaylistItem playlistItem) { + return playlistItem.periods.isEmpty() ? 1 : playlistItem.periods.size(); + } + } + + /** + * An immutable description of a playlist item, containing both static setup information like + * {@link MediaItem} and dynamic data that is generally read from the media like the duration. + */ + protected static final class PlaylistItem { + + /** A builder for {@link PlaylistItem} objects. */ + public static final class Builder { + + private Object uid; + private Tracks tracks; + private MediaItem mediaItem; + @Nullable private MediaMetadata mediaMetadata; + @Nullable private Object manifest; + @Nullable private MediaItem.LiveConfiguration liveConfiguration; + private long presentationStartTimeMs; + private long windowStartTimeMs; + private long elapsedRealtimeEpochOffsetMs; + private boolean isSeekable; + private boolean isDynamic; + private long defaultPositionUs; + private long durationUs; + private long positionInFirstPeriodUs; + private boolean isPlaceholder; + private ImmutableList periods; + + /** + * Creates the builder. + * + * @param uid The unique identifier of the playlist item within a playlist. This value will be + * set as {@link Timeline.Window#uid} for this item. + */ + public Builder(Object uid) { + this.uid = uid; + tracks = Tracks.EMPTY; + mediaItem = MediaItem.EMPTY; + mediaMetadata = null; + manifest = null; + liveConfiguration = null; + presentationStartTimeMs = C.TIME_UNSET; + windowStartTimeMs = C.TIME_UNSET; + elapsedRealtimeEpochOffsetMs = C.TIME_UNSET; + isSeekable = false; + isDynamic = false; + defaultPositionUs = 0; + durationUs = C.TIME_UNSET; + positionInFirstPeriodUs = 0; + isPlaceholder = false; + periods = ImmutableList.of(); + } + + private Builder(PlaylistItem playlistItem) { + this.uid = playlistItem.uid; + this.tracks = playlistItem.tracks; + this.mediaItem = playlistItem.mediaItem; + this.mediaMetadata = playlistItem.mediaMetadata; + this.manifest = playlistItem.manifest; + this.liveConfiguration = playlistItem.liveConfiguration; + this.presentationStartTimeMs = playlistItem.presentationStartTimeMs; + this.windowStartTimeMs = playlistItem.windowStartTimeMs; + this.elapsedRealtimeEpochOffsetMs = playlistItem.elapsedRealtimeEpochOffsetMs; + this.isSeekable = playlistItem.isSeekable; + this.isDynamic = playlistItem.isDynamic; + this.defaultPositionUs = playlistItem.defaultPositionUs; + this.durationUs = playlistItem.durationUs; + this.positionInFirstPeriodUs = playlistItem.positionInFirstPeriodUs; + this.isPlaceholder = playlistItem.isPlaceholder; + this.periods = playlistItem.periods; + } + + /** + * Sets the unique identifier of this playlist item within a playlist. + * + *

    This value will be set as {@link Timeline.Window#uid} for this item. + * + * @param uid The unique identifier of this playlist item within a playlist. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setUid(Object uid) { + this.uid = uid; + return this; + } + + /** + * Sets the {@link Tracks} of this playlist item. + * + * @param tracks The {@link Tracks} of this playlist item. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setTracks(Tracks tracks) { + this.tracks = tracks; + return this; + } + + /** + * Sets the {@link MediaItem} for this playlist item. + * + * @param mediaItem The {@link MediaItem} for this playlist item. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setMediaItem(MediaItem mediaItem) { + this.mediaItem = mediaItem; + return this; + } + + /** + * Sets the {@link MediaMetadata}. + * + *

    This data includes static data from the {@link MediaItem#mediaMetadata MediaItem} and + * the media's {@link Format#metadata Format}, as well any dynamic metadata that has been + * parsed from the media. If null, the metadata is assumed to be the simple combination of the + * {@link MediaItem#mediaMetadata MediaItem} metadata and the metadata of the selected {@link + * Format#metadata Formats}. + * + * @param mediaMetadata The {@link MediaMetadata}, or null to assume that the metadata is the + * simple combination of the {@link MediaItem#mediaMetadata MediaItem} metadata and the + * metadata of the selected {@link Format#metadata Formats}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setMediaMetadata(@Nullable MediaMetadata mediaMetadata) { + this.mediaMetadata = mediaMetadata; + return this; + } + + /** + * Sets the manifest of the playlist item. + * + * @param manifest The manifest of the playlist item, or null if not applicable. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setManifest(@Nullable Object manifest) { + this.manifest = manifest; + return this; + } + + /** + * Sets the active {@link MediaItem.LiveConfiguration}, or null if the playlist item is not + * live. + * + * @param liveConfiguration The active {@link MediaItem.LiveConfiguration}, or null if the + * playlist item is not live. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setLiveConfiguration(@Nullable MediaItem.LiveConfiguration liveConfiguration) { + this.liveConfiguration = liveConfiguration; + return this; + } + + /** + * Sets the start time of the live presentation. + * + *

    This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is + * {@linkplain #setLiveConfiguration live}. + * + * @param presentationStartTimeMs The start time of the live presentation, in milliseconds + * since the Unix epoch, or {@link C#TIME_UNSET} if unknown or not applicable. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPresentationStartTimeMs(long presentationStartTimeMs) { + this.presentationStartTimeMs = presentationStartTimeMs; + return this; + } + + /** + * Sets the start time of the live window. + * + *

    This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is + * {@linkplain #setLiveConfiguration live}. The value should also be greater or equal than the + * {@linkplain #setPresentationStartTimeMs presentation start time}, if set. + * + * @param windowStartTimeMs The start time of the live window, in milliseconds since the Unix + * epoch, or {@link C#TIME_UNSET} if unknown or not applicable. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setWindowStartTimeMs(long windowStartTimeMs) { + this.windowStartTimeMs = windowStartTimeMs; + return this; + } + + /** + * Sets the offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix + * epoch according to the clock of the media origin server. + * + *

    This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is + * {@linkplain #setLiveConfiguration live}. + * + * @param elapsedRealtimeEpochOffsetMs The offset between {@link + * SystemClock#elapsedRealtime()} and the time since the Unix epoch according to the clock + * of the media origin server, or {@link C#TIME_UNSET} if unknown or not applicable. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setElapsedRealtimeEpochOffsetMs(long elapsedRealtimeEpochOffsetMs) { + this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs; + return this; + } + + /** + * Sets whether it's possible to seek within this playlist item. + * + * @param isSeekable Whether it's possible to seek within this playlist item. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsSeekable(boolean isSeekable) { + this.isSeekable = isSeekable; + return this; + } + + /** + * Sets whether this playlist item may change over time, for example a moving live window. + * + * @param isDynamic Whether this playlist item may change over time, for example a moving live + * window. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsDynamic(boolean isDynamic) { + this.isDynamic = isDynamic; + return this; + } + + /** + * Sets the default position relative to the start of the playlist item at which to begin + * playback, in microseconds. + * + *

    The default position must be less or equal to the {@linkplain #setDurationUs duration}, + * is set. + * + * @param defaultPositionUs The default position relative to the start of the playlist item at + * which to begin playback, in microseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setDefaultPositionUs(long defaultPositionUs) { + checkArgument(defaultPositionUs >= 0); + this.defaultPositionUs = defaultPositionUs; + return this; + } + + /** + * Sets the duration of the playlist item, in microseconds. + * + *

    If both this duration and all {@linkplain #setPeriods period} durations are set, the sum + * of this duration and the {@linkplain #setPositionInFirstPeriodUs offset in the first + * period} must match the total duration of all periods. + * + * @param durationUs The duration of the playlist item, in microseconds, or {@link + * C#TIME_UNSET} if unknown. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setDurationUs(long durationUs) { + checkArgument(durationUs == C.TIME_UNSET || durationUs >= 0); + this.durationUs = durationUs; + return this; + } + + /** + * Sets the position of the start of this playlist item relative to the start of the first + * period belonging to it, in microseconds. + * + * @param positionInFirstPeriodUs The position of the start of this playlist item relative to + * the start of the first period belonging to it, in microseconds. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPositionInFirstPeriodUs(long positionInFirstPeriodUs) { + checkArgument(positionInFirstPeriodUs >= 0); + this.positionInFirstPeriodUs = positionInFirstPeriodUs; + return this; + } + + /** + * Sets whether this playlist item contains placeholder information because the real + * information has yet to be loaded. + * + * @param isPlaceholder Whether this playlist item contains placeholder information because + * the real information has yet to be loaded. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsPlaceholder(boolean isPlaceholder) { + this.isPlaceholder = isPlaceholder; + return this; + } + + /** + * Sets the list of {@linkplain PeriodData periods} in this playlist item. + * + *

    All periods must have unique {@linkplain PeriodData.Builder#setUid UIDs} and only the + * last period is allowed to have an unset {@linkplain PeriodData.Builder#setDurationUs + * duration}. + * + * @param periods The list of {@linkplain PeriodData periods} in this playlist item, or an + * empty list to assume a single period without ads and the same duration as the playlist + * item. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPeriods(List periods) { + int periodCount = periods.size(); + for (int i = 0; i < periodCount - 1; i++) { + checkArgument(periods.get(i).durationUs != C.TIME_UNSET); + for (int j = i + 1; j < periodCount; j++) { + checkArgument(!periods.get(i).uid.equals(periods.get(j).uid)); + } + } + this.periods = ImmutableList.copyOf(periods); + return this; + } + + /** Builds the {@link PlaylistItem}. */ + public PlaylistItem build() { + return new PlaylistItem(this); + } + } + + /** The unique identifier of this playlist item. */ + public final Object uid; + /** The {@link Tracks} of this playlist item. */ + public final Tracks tracks; + /** The {@link MediaItem} for this playlist item. */ + public final MediaItem mediaItem; + /** + * The {@link MediaMetadata}, including static data from the {@link MediaItem#mediaMetadata + * MediaItem} and the media's {@link Format#metadata Format}, as well any dynamic metadata that + * has been parsed from the media. If null, the metadata is assumed to be the simple combination + * of the {@link MediaItem#mediaMetadata MediaItem} metadata and the metadata of the selected + * {@link Format#metadata Formats}. + */ + @Nullable public final MediaMetadata mediaMetadata; + /** The manifest of the playlist item, or null if not applicable. */ + @Nullable public final Object manifest; + /** The active {@link MediaItem.LiveConfiguration}, or null if the playlist item is not live. */ + @Nullable public final MediaItem.LiveConfiguration liveConfiguration; + /** + * The start time of the live presentation, in milliseconds since the Unix epoch, or {@link + * C#TIME_UNSET} if unknown or not applicable. + */ + public final long presentationStartTimeMs; + /** + * The start time of the live window, in milliseconds since the Unix epoch, or {@link + * C#TIME_UNSET} if unknown or not applicable. + */ + public final long windowStartTimeMs; + /** + * The offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix epoch + * according to the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not + * applicable. + */ + public final long elapsedRealtimeEpochOffsetMs; + /** Whether it's possible to seek within this playlist item. */ + public final boolean isSeekable; + /** Whether this playlist item may change over time, for example a moving live window. */ + public final boolean isDynamic; + /** + * The default position relative to the start of the playlist item at which to begin playback, + * in microseconds. + */ + public final long defaultPositionUs; + /** The duration of the playlist item, in microseconds, or {@link C#TIME_UNSET} if unknown. */ + public final long durationUs; + /** + * The position of the start of this playlist item relative to the start of the first period + * belonging to it, in microseconds. + */ + public final long positionInFirstPeriodUs; + /** + * Whether this playlist item contains placeholder information because the real information has + * yet to be loaded. + */ + public final boolean isPlaceholder; + /** + * The list of {@linkplain PeriodData periods} in this playlist item, or an empty list to assume + * a single period without ads and the same duration as the playlist item. + */ + public final ImmutableList periods; + + private final long[] periodPositionInWindowUs; + private final MediaMetadata combinedMediaMetadata; + + private PlaylistItem(Builder builder) { + if (builder.liveConfiguration == null) { + checkArgument(builder.presentationStartTimeMs == C.TIME_UNSET); + checkArgument(builder.windowStartTimeMs == C.TIME_UNSET); + checkArgument(builder.elapsedRealtimeEpochOffsetMs == C.TIME_UNSET); + } else if (builder.presentationStartTimeMs != C.TIME_UNSET + && builder.windowStartTimeMs != C.TIME_UNSET) { + checkArgument(builder.windowStartTimeMs >= builder.presentationStartTimeMs); + } + int periodCount = builder.periods.size(); + if (builder.durationUs != C.TIME_UNSET) { + checkArgument(builder.defaultPositionUs <= builder.durationUs); + } + this.uid = builder.uid; + this.tracks = builder.tracks; + this.mediaItem = builder.mediaItem; + this.mediaMetadata = builder.mediaMetadata; + this.manifest = builder.manifest; + this.liveConfiguration = builder.liveConfiguration; + this.presentationStartTimeMs = builder.presentationStartTimeMs; + this.windowStartTimeMs = builder.windowStartTimeMs; + this.elapsedRealtimeEpochOffsetMs = builder.elapsedRealtimeEpochOffsetMs; + this.isSeekable = builder.isSeekable; + this.isDynamic = builder.isDynamic; + this.defaultPositionUs = builder.defaultPositionUs; + this.durationUs = builder.durationUs; + this.positionInFirstPeriodUs = builder.positionInFirstPeriodUs; + this.isPlaceholder = builder.isPlaceholder; + this.periods = builder.periods; + periodPositionInWindowUs = new long[periods.size()]; + if (!periods.isEmpty()) { + periodPositionInWindowUs[0] = -positionInFirstPeriodUs; + for (int i = 0; i < periodCount - 1; i++) { + periodPositionInWindowUs[i + 1] = periodPositionInWindowUs[i] + periods.get(i).durationUs; + } + } + combinedMediaMetadata = + mediaMetadata != null ? mediaMetadata : getCombinedMediaMetadata(mediaItem, tracks); + } + + /** Returns a {@link Builder} pre-populated with the current values. */ + public Builder buildUpon() { + return new Builder(this); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PlaylistItem)) { + return false; + } + PlaylistItem playlistItem = (PlaylistItem) o; + return this.uid.equals(playlistItem.uid) + && this.tracks.equals(playlistItem.tracks) + && this.mediaItem.equals(playlistItem.mediaItem) + && Util.areEqual(this.mediaMetadata, playlistItem.mediaMetadata) + && Util.areEqual(this.manifest, playlistItem.manifest) + && Util.areEqual(this.liveConfiguration, playlistItem.liveConfiguration) + && this.presentationStartTimeMs == playlistItem.presentationStartTimeMs + && this.windowStartTimeMs == playlistItem.windowStartTimeMs + && this.elapsedRealtimeEpochOffsetMs == playlistItem.elapsedRealtimeEpochOffsetMs + && this.isSeekable == playlistItem.isSeekable + && this.isDynamic == playlistItem.isDynamic + && this.defaultPositionUs == playlistItem.defaultPositionUs + && this.durationUs == playlistItem.durationUs + && this.positionInFirstPeriodUs == playlistItem.positionInFirstPeriodUs + && this.isPlaceholder == playlistItem.isPlaceholder + && this.periods.equals(playlistItem.periods); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + uid.hashCode(); + result = 31 * result + tracks.hashCode(); + result = 31 * result + mediaItem.hashCode(); + result = 31 * result + (mediaMetadata == null ? 0 : mediaMetadata.hashCode()); + result = 31 * result + (manifest == null ? 0 : manifest.hashCode()); + result = 31 * result + (liveConfiguration == null ? 0 : liveConfiguration.hashCode()); + result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32)); + result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32)); + result = + 31 * result + + (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32)); + result = 31 * result + (isSeekable ? 1 : 0); + result = 31 * result + (isDynamic ? 1 : 0); + result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32)); + result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); + result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32)); + result = 31 * result + (isPlaceholder ? 1 : 0); + result = 31 * result + periods.hashCode(); + return result; + } + + private Timeline.Window getWindow(int firstPeriodIndex, Timeline.Window window) { + int periodCount = periods.isEmpty() ? 1 : periods.size(); + window.set( + uid, + mediaItem, + manifest, + presentationStartTimeMs, + windowStartTimeMs, + elapsedRealtimeEpochOffsetMs, + isSeekable, + isDynamic, + liveConfiguration, + defaultPositionUs, + durationUs, + firstPeriodIndex, + /* lastPeriodIndex= */ firstPeriodIndex + periodCount - 1, + positionInFirstPeriodUs); + window.isPlaceholder = isPlaceholder; + return window; + } + + private Timeline.Period getPeriod( + int windowIndex, int periodIndexInPlaylistItem, Timeline.Period period) { + if (periods.isEmpty()) { + period.set( + /* id= */ uid, + uid, + windowIndex, + /* durationUs= */ positionInFirstPeriodUs + durationUs, + /* positionInWindowUs= */ 0, + AdPlaybackState.NONE, + isPlaceholder); + } else { + PeriodData periodData = periods.get(periodIndexInPlaylistItem); + Object periodId = periodData.uid; + Object periodUid = Pair.create(uid, periodId); + period.set( + periodId, + periodUid, + windowIndex, + periodData.durationUs, + periodPositionInWindowUs[periodIndexInPlaylistItem], + periodData.adPlaybackState, + periodData.isPlaceholder); + } + return period; + } + + private Object getPeriodUid(int periodIndexInPlaylistItem) { + if (periods.isEmpty()) { + return uid; + } + Object periodId = periods.get(periodIndexInPlaylistItem).uid; + return Pair.create(uid, periodId); + } + + private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) { + MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); + int trackGroupCount = tracks.getGroups().size(); + for (int i = 0; i < trackGroupCount; i++) { + Tracks.Group group = tracks.getGroups().get(i); + for (int j = 0; j < group.length; j++) { + if (group.isTrackSelected(j)) { + Format format = group.getTrackFormat(j); + if (format.metadata != null) { + for (int k = 0; k < format.metadata.length(); k++) { + format.metadata.get(k).populateMediaMetadata(metadataBuilder); + } + } + } + } + } + return metadataBuilder.populate(mediaItem.mediaMetadata).build(); + } + } + + /** Data describing the properties of a period inside a {@link PlaylistItem}. */ + protected static final class PeriodData { + + /** A builder for {@link PeriodData} objects. */ + public static final class Builder { + + private Object uid; + private long durationUs; + private AdPlaybackState adPlaybackState; + private boolean isPlaceholder; + + /** + * Creates the builder. + * + * @param uid The unique identifier of the period within its playlist item. + */ + public Builder(Object uid) { + this.uid = uid; + this.durationUs = 0; + this.adPlaybackState = AdPlaybackState.NONE; + this.isPlaceholder = false; + } + + private Builder(PeriodData periodData) { + this.uid = periodData.uid; + this.durationUs = periodData.durationUs; + this.adPlaybackState = periodData.adPlaybackState; + this.isPlaceholder = periodData.isPlaceholder; + } + + /** + * Sets the unique identifier of the period within its playlist item. + * + * @param uid The unique identifier of the period within its playlist item. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setUid(Object uid) { + this.uid = uid; + return this; + } + + /** + * Sets the total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. + * + *

    Only the last period in a playlist item can have an unknown duration. + * + * @param durationUs The total duration of the period, in microseconds, or {@link + * C#TIME_UNSET} if unknown. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setDurationUs(long durationUs) { + checkArgument(durationUs == C.TIME_UNSET || durationUs >= 0); + this.durationUs = durationUs; + return this; + } + + /** + * Sets the {@link AdPlaybackState}. + * + * @param adPlaybackState The {@link AdPlaybackState}, or {@link AdPlaybackState#NONE} if + * there are no ads. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setAdPlaybackState(AdPlaybackState adPlaybackState) { + this.adPlaybackState = adPlaybackState; + return this; + } + + /** + * Sets whether this period contains placeholder information because the real information has + * yet to be loaded + * + * @param isPlaceholder Whether this period contains placeholder information because the real + * information has yet to be loaded. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setIsPlaceholder(boolean isPlaceholder) { + this.isPlaceholder = isPlaceholder; + return this; + } + + /** Builds the {@link PeriodData}. */ + public PeriodData build() { + return new PeriodData(this); + } + } + + /** The unique identifier of the period within its playlist item. */ + public final Object uid; + /** + * The total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. Only + * the last period in a playlist item can have an unknown duration. + */ + public final long durationUs; + /** + * The {@link AdPlaybackState} of the period, or {@link AdPlaybackState#NONE} if there are no + * ads. + */ + public final AdPlaybackState adPlaybackState; + /** + * Whether this period contains placeholder information because the real information has yet to + * be loaded. + */ + public final boolean isPlaceholder; + + private PeriodData(Builder builder) { + this.uid = builder.uid; + this.durationUs = builder.durationUs; + this.adPlaybackState = builder.adPlaybackState; + this.isPlaceholder = builder.isPlaceholder; + } + + /** Returns a {@link Builder} pre-populated with the current values. */ + public Builder buildUpon() { + return new Builder(this); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PeriodData)) { + return false; + } + PeriodData periodData = (PeriodData) o; + return this.uid.equals(periodData.uid) + && this.durationUs == periodData.durationUs + && this.adPlaybackState.equals(periodData.adPlaybackState) + && this.isPlaceholder == periodData.isPlaceholder; + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + uid.hashCode(); + result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); + result = 31 * result + adPlaybackState.hashCode(); + result = 31 * result + (isPlaceholder ? 1 : 0); + return result; + } + } + + /** A supplier for a position. */ + protected interface PositionSupplier { + + /** An instance returning a constant position of zero. */ + PositionSupplier ZERO = getConstant(/* positionMs= */ 0); + + /** + * Returns an instance that returns a constant value. + * + * @param positionMs The constant position to return, in milliseconds. + */ + static PositionSupplier getConstant(long positionMs) { + return () -> positionMs; + } + + /** + * Returns an instance that extrapolates the provided position into the future. + * + * @param currentPositionMs The current position in milliseconds. + * @param playbackSpeed The playback speed with which the position is assumed to increase. + */ + static PositionSupplier getExtrapolating(long currentPositionMs, float playbackSpeed) { + long startTimeMs = SystemClock.elapsedRealtime(); + return () -> { + long currentTimeMs = SystemClock.elapsedRealtime(); + return currentPositionMs + (long) ((currentTimeMs - startTimeMs) * playbackSpeed); + }; + } + + /** Returns the position. */ + long get(); + } + + /** + * Position difference threshold below which we do not automatically report a position + * discontinuity, in milliseconds. + */ + private static final long POSITION_DISCONTINUITY_THRESHOLD_MS = 1000; + private final ListenerSet listeners; private final Looper applicationLooper; private final HandlerWrapper applicationHandler; private final HashSet> pendingOperations; + private final Timeline.Period period; private @MonotonicNonNull State state; @@ -209,6 +2000,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.applicationLooper = applicationLooper; applicationHandler = clock.createHandler(applicationLooper, /* callback= */ null); pendingOperations = new HashSet<>(); + period = new Timeline.Period(); @SuppressWarnings("nullness:argument.type.incompatible") // Using this in constructor. ListenerSet listenerSet = new ListenerSet<>( @@ -303,34 +2095,36 @@ public abstract class SimpleBasePlayer extends BasePlayer { } @Override + @Player.State public final int getPlaybackState() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.playbackState; } @Override public final int getPlaybackSuppressionReason() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.playbackSuppressionReason; } @Nullable @Override public final PlaybackException getPlayerError() { + verifyApplicationThreadAndInitState(); + return state.playerError; + } + + @Override + public final void setRepeatMode(@Player.RepeatMode int repeatMode) { // TODO: implement. throw new IllegalStateException(); } @Override - public final void setRepeatMode(int repeatMode) { - // TODO: implement. - throw new IllegalStateException(); - } - - @Override + @Player.RepeatMode public final int getRepeatMode() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.repeatMode; } @Override @@ -341,14 +2135,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final boolean getShuffleModeEnabled() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.shuffleModeEnabled; } @Override public final boolean isLoading() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.isLoading; } @Override @@ -359,20 +2153,20 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final long getSeekBackIncrement() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.seekBackIncrementMs; } @Override public final long getSeekForwardIncrement() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.seekForwardIncrementMs; } @Override public final long getMaxSeekToPreviousPosition() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.maxSeekToPreviousPositionMs; } @Override @@ -383,8 +2177,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final PlaybackParameters getPlaybackParameters() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.playbackParameters; } @Override @@ -407,14 +2201,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final Tracks getCurrentTracks() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return getCurrentTracksInternal(state); } @Override public final TrackSelectionParameters getTrackSelectionParameters() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.trackSelectionParameters; } @Override @@ -425,14 +2219,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final MediaMetadata getMediaMetadata() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return getMediaMetadataInternal(state); } @Override public final MediaMetadata getPlaylistMetadata() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.playlistMetadata; } @Override @@ -443,80 +2237,89 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final Timeline getCurrentTimeline() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.timeline; } @Override public final int getCurrentPeriodIndex() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return getCurrentPeriodIndexInternal(state, window); } @Override public final int getCurrentMediaItemIndex() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.currentMediaItemIndex; } @Override public final long getDuration() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + if (isPlayingAd()) { + state.timeline.getPeriod(getCurrentPeriodIndex(), period); + long adDurationUs = + period.getAdDurationUs(state.currentAdGroupIndex, state.currentAdIndexInAdGroup); + return Util.usToMs(adDurationUs); + } + return getContentDuration(); } @Override public final long getCurrentPosition() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return isPlayingAd() ? state.adPositionMsSupplier.get() : getContentPosition(); } @Override public final long getBufferedPosition() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return isPlayingAd() + ? max(state.adBufferedPositionMsSupplier.get(), state.adPositionMsSupplier.get()) + : getContentBufferedPosition(); } @Override public final long getTotalBufferedDuration() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.totalBufferedDurationMsSupplier.get(); } @Override public final boolean isPlayingAd() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.currentAdGroupIndex != C.INDEX_UNSET; } @Override public final int getCurrentAdGroupIndex() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.currentAdGroupIndex; } @Override public final int getCurrentAdIndexInAdGroup() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.currentAdIndexInAdGroup; } @Override public final long getContentPosition() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.contentPositionMsSupplier.get(); } @Override public final long getContentBufferedPosition() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return max( + state.contentBufferedPositionMsSupplier.get(), state.contentPositionMsSupplier.get()); } @Override public final AudioAttributes getAudioAttributes() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.audioAttributes; } @Override @@ -527,8 +2330,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final float getVolume() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.volume; } @Override @@ -587,38 +2390,38 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final VideoSize getVideoSize() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.videoSize; } @Override public final Size getSurfaceSize() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.surfaceSize; } @Override public final CueGroup getCurrentCues() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.currentCues; } @Override public final DeviceInfo getDeviceInfo() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.deviceInfo; } @Override public final int getDeviceVolume() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.deviceVolume; } @Override public final boolean isDeviceMuted() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + return state.isDeviceMuted; } @Override @@ -721,11 +2524,95 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.state = newState; boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady; - if (playWhenReadyChanged /* TODO: || playbackStateChanged */) { + boolean playbackStateChanged = previousState.playbackState != newState.playbackState; + Tracks previousTracks = getCurrentTracksInternal(previousState); + Tracks newTracks = getCurrentTracksInternal(newState); + MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState); + MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState); + int positionDiscontinuityReason = + getPositionDiscontinuityReason(previousState, newState, window, period); + boolean timelineChanged = !previousState.timeline.equals(newState.timeline); + int mediaItemTransitionReason = + getMediaItemTransitionReason(previousState, newState, positionDiscontinuityReason, window); + + if (timelineChanged) { + @Player.TimelineChangeReason + int timelineChangeReason = + getTimelineChangeReason(previousState.playlistItems, newState.playlistItems); + listeners.queueEvent( + Player.EVENT_TIMELINE_CHANGED, + listener -> listener.onTimelineChanged(newState.timeline, timelineChangeReason)); + } + if (positionDiscontinuityReason != C.INDEX_UNSET) { + PositionInfo previousPositionInfo = + getPositionInfo(previousState, /* useDiscontinuityPosition= */ false, window, period); + PositionInfo positionInfo = + getPositionInfo( + newState, + /* useDiscontinuityPosition= */ state.hasPositionDiscontinuity, + window, + period); + listeners.queueEvent( + Player.EVENT_POSITION_DISCONTINUITY, + listener -> { + listener.onPositionDiscontinuity(positionDiscontinuityReason); + listener.onPositionDiscontinuity( + previousPositionInfo, positionInfo, positionDiscontinuityReason); + }); + } + if (mediaItemTransitionReason != C.INDEX_UNSET) { + @Nullable + MediaItem mediaItem = + state.timeline.isEmpty() + ? null + : state.playlistItems.get(state.currentMediaItemIndex).mediaItem; + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); + } + if (!Util.areEqual(previousState.playerError, newState.playerError)) { + listeners.queueEvent( + Player.EVENT_PLAYER_ERROR, + listener -> listener.onPlayerErrorChanged(newState.playerError)); + if (newState.playerError != null) { + listeners.queueEvent( + Player.EVENT_PLAYER_ERROR, + listener -> listener.onPlayerError(castNonNull(newState.playerError))); + } + } + if (!previousState.trackSelectionParameters.equals(newState.trackSelectionParameters)) { + listeners.queueEvent( + Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, + listener -> + listener.onTrackSelectionParametersChanged(newState.trackSelectionParameters)); + } + if (!previousTracks.equals(newTracks)) { + listeners.queueEvent( + Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newTracks)); + } + if (!previousMediaMetadata.equals(newMediaMetadata)) { + listeners.queueEvent( + EVENT_MEDIA_METADATA_CHANGED, + listener -> listener.onMediaMetadataChanged(newMediaMetadata)); + } + if (previousState.isLoading != newState.isLoading) { + listeners.queueEvent( + Player.EVENT_IS_LOADING_CHANGED, + listener -> { + listener.onLoadingChanged(newState.isLoading); + listener.onIsLoadingChanged(newState.isLoading); + }); + } + if (playWhenReadyChanged || playbackStateChanged) { listeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> - listener.onPlayerStateChanged(newState.playWhenReady, /* TODO */ Player.STATE_IDLE)); + listener.onPlayerStateChanged(newState.playWhenReady, newState.playbackState)); + } + if (playbackStateChanged) { + listeners.queueEvent( + Player.EVENT_PLAYBACK_STATE_CHANGED, + listener -> listener.onPlaybackStateChanged(newState.playbackState)); } if (playWhenReadyChanged || previousState.playWhenReadyChangeReason != newState.playWhenReadyChangeReason) { @@ -735,11 +2622,115 @@ public abstract class SimpleBasePlayer extends BasePlayer { listener.onPlayWhenReadyChanged( newState.playWhenReady, newState.playWhenReadyChangeReason)); } + if (previousState.playbackSuppressionReason != newState.playbackSuppressionReason) { + listeners.queueEvent( + Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, + listener -> + listener.onPlaybackSuppressionReasonChanged(newState.playbackSuppressionReason)); + } if (isPlaying(previousState) != isPlaying(newState)) { listeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newState))); } + if (!previousState.playbackParameters.equals(newState.playbackParameters)) { + listeners.queueEvent( + Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, + listener -> listener.onPlaybackParametersChanged(newState.playbackParameters)); + } + if (previousState.skipSilenceEnabled != newState.skipSilenceEnabled) { + listeners.queueEvent( + Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, + listener -> listener.onSkipSilenceEnabledChanged(newState.skipSilenceEnabled)); + } + if (previousState.repeatMode != newState.repeatMode) { + listeners.queueEvent( + Player.EVENT_REPEAT_MODE_CHANGED, + listener -> listener.onRepeatModeChanged(newState.repeatMode)); + } + if (previousState.shuffleModeEnabled != newState.shuffleModeEnabled) { + listeners.queueEvent( + Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, + listener -> listener.onShuffleModeEnabledChanged(newState.shuffleModeEnabled)); + } + if (previousState.seekBackIncrementMs != newState.seekBackIncrementMs) { + listeners.queueEvent( + Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, + listener -> listener.onSeekBackIncrementChanged(newState.seekBackIncrementMs)); + } + if (previousState.seekForwardIncrementMs != newState.seekForwardIncrementMs) { + listeners.queueEvent( + Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, + listener -> listener.onSeekForwardIncrementChanged(newState.seekForwardIncrementMs)); + } + if (previousState.maxSeekToPreviousPositionMs != newState.maxSeekToPreviousPositionMs) { + listeners.queueEvent( + Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, + listener -> + listener.onMaxSeekToPreviousPositionChanged(newState.maxSeekToPreviousPositionMs)); + } + if (!previousState.audioAttributes.equals(newState.audioAttributes)) { + listeners.queueEvent( + Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, + listener -> listener.onAudioAttributesChanged(newState.audioAttributes)); + } + if (!previousState.videoSize.equals(newState.videoSize)) { + listeners.queueEvent( + Player.EVENT_VIDEO_SIZE_CHANGED, + listener -> listener.onVideoSizeChanged(newState.videoSize)); + } + if (!previousState.deviceInfo.equals(newState.deviceInfo)) { + listeners.queueEvent( + Player.EVENT_DEVICE_INFO_CHANGED, + listener -> listener.onDeviceInfoChanged(newState.deviceInfo)); + } + if (!previousState.playlistMetadata.equals(newState.playlistMetadata)) { + listeners.queueEvent( + Player.EVENT_PLAYLIST_METADATA_CHANGED, + listener -> listener.onPlaylistMetadataChanged(newState.playlistMetadata)); + } + if (previousState.audioSessionId != newState.audioSessionId) { + listeners.queueEvent( + Player.EVENT_AUDIO_SESSION_ID, + listener -> listener.onAudioSessionIdChanged(newState.audioSessionId)); + } + if (newState.newlyRenderedFirstFrame) { + listeners.queueEvent(Player.EVENT_RENDERED_FIRST_FRAME, Listener::onRenderedFirstFrame); + } + if (!previousState.surfaceSize.equals(newState.surfaceSize)) { + listeners.queueEvent( + Player.EVENT_SURFACE_SIZE_CHANGED, + listener -> + listener.onSurfaceSizeChanged( + newState.surfaceSize.getWidth(), newState.surfaceSize.getHeight())); + } + if (previousState.volume != newState.volume) { + listeners.queueEvent( + Player.EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(newState.volume)); + } + if (previousState.deviceVolume != newState.deviceVolume + || previousState.isDeviceMuted != newState.isDeviceMuted) { + listeners.queueEvent( + Player.EVENT_DEVICE_VOLUME_CHANGED, + listener -> + listener.onDeviceVolumeChanged(newState.deviceVolume, newState.isDeviceMuted)); + } + if (!previousState.currentCues.equals(newState.currentCues)) { + listeners.queueEvent( + Player.EVENT_CUES, + listener -> { + listener.onCues(newState.currentCues.cues); + listener.onCues(newState.currentCues); + }); + } + if (!previousState.timedMetadata.equals(newState.timedMetadata) + && newState.timedMetadata.presentationTimeUs != C.TIME_UNSET) { + listeners.queueEvent( + Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata)); + } + if (false /* TODO: add flag to know when a seek request has been resolved */) { + listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed); + } if (!previousState.availableCommands.equals(newState.availableCommands)) { listeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, @@ -777,7 +2768,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { updateStateAndInformListeners(getPlaceholderState(suggestedPlaceholderState)); pendingOperation.addListener( () -> { - castNonNull(state); // Already check by method @RequiresNonNull pre-condition. + castNonNull(state); // Already checked by method @RequiresNonNull pre-condition. pendingOperations.remove(pendingOperation); if (pendingOperations.isEmpty()) { updateStateAndInformListeners(getState()); @@ -796,8 +2787,191 @@ public abstract class SimpleBasePlayer extends BasePlayer { } private static boolean isPlaying(State state) { - return state.playWhenReady && false; - // TODO: && state.playbackState == Player.STATE_READY - // && state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE + return state.playWhenReady + && state.playbackState == Player.STATE_READY + && state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE; + } + + private static Tracks getCurrentTracksInternal(State state) { + return state.playlistItems.isEmpty() + ? Tracks.EMPTY + : state.playlistItems.get(state.currentMediaItemIndex).tracks; + } + + private static MediaMetadata getMediaMetadataInternal(State state) { + return state.playlistItems.isEmpty() + ? MediaMetadata.EMPTY + : state.playlistItems.get(state.currentMediaItemIndex).combinedMediaMetadata; + } + + private static int getCurrentPeriodIndexInternal(State state, Timeline.Window window) { + if (state.currentPeriodIndex != C.INDEX_UNSET) { + return state.currentPeriodIndex; + } + if (state.timeline.isEmpty()) { + return state.currentMediaItemIndex; + } + return state.timeline.getWindow(state.currentMediaItemIndex, window).firstPeriodIndex; + } + + private static @Player.TimelineChangeReason int getTimelineChangeReason( + List previousPlaylist, List newPlaylist) { + if (previousPlaylist.size() != newPlaylist.size()) { + return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; + } + for (int i = 0; i < previousPlaylist.size(); i++) { + if (!previousPlaylist.get(i).uid.equals(newPlaylist.get(i).uid)) { + return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; + } + } + return Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE; + } + + private static int getPositionDiscontinuityReason( + State previousState, State newState, Timeline.Window window, Timeline.Period period) { + if (newState.hasPositionDiscontinuity) { + // We were asked to report a discontinuity. + return newState.positionDiscontinuityReason; + } + if (previousState.playlistItems.isEmpty()) { + // First change from an empty timeline is not reported as a discontinuity. + return C.INDEX_UNSET; + } + if (newState.playlistItems.isEmpty()) { + // The playlist became empty. + return Player.DISCONTINUITY_REASON_REMOVE; + } + Object previousPeriodUid = + previousState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(previousState, window)); + Object newPeriodUid = + newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window)); + if (!newPeriodUid.equals(previousPeriodUid) + || previousState.currentAdGroupIndex != newState.currentAdGroupIndex + || previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) { + // The current period or ad inside a period changed. + if (newState.timeline.getIndexOfPeriod(previousPeriodUid) == C.INDEX_UNSET) { + // The previous period no longer exists. + return Player.DISCONTINUITY_REASON_REMOVE; + } + // Check if reached the previous period's or ad's duration to assume an auto-transition. + long previousPositionMs = + getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period); + long previousDurationMs = getPeriodOrAdDurationMs(previousState, previousPeriodUid, period); + return previousDurationMs != C.TIME_UNSET && previousPositionMs >= previousDurationMs + ? Player.DISCONTINUITY_REASON_AUTO_TRANSITION + : Player.DISCONTINUITY_REASON_SKIP; + } + // We are in the same content period or ad. Check if the position deviates more than a + // reasonable threshold from the previous one. + long previousPositionMs = + getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period); + long newPositionMs = getCurrentPeriodOrAdPositionMs(newState, newPeriodUid, period); + if (Math.abs(previousPositionMs - newPositionMs) < POSITION_DISCONTINUITY_THRESHOLD_MS) { + return C.INDEX_UNSET; + } + // Check if we previously reached the end of the item to assume an auto-repetition. + long previousDurationMs = getPeriodOrAdDurationMs(previousState, previousPeriodUid, period); + return previousDurationMs != C.TIME_UNSET && previousPositionMs >= previousDurationMs + ? Player.DISCONTINUITY_REASON_AUTO_TRANSITION + : Player.DISCONTINUITY_REASON_INTERNAL; + } + + private static long getCurrentPeriodOrAdPositionMs( + State state, Object currentPeriodUid, Timeline.Period period) { + return state.currentAdGroupIndex != C.INDEX_UNSET + ? state.adPositionMsSupplier.get() + : state.contentPositionMsSupplier.get() + - state.timeline.getPeriodByUid(currentPeriodUid, period).getPositionInWindowMs(); + } + + private static long getPeriodOrAdDurationMs( + State state, Object currentPeriodUid, Timeline.Period period) { + state.timeline.getPeriodByUid(currentPeriodUid, period); + long periodOrAdDurationUs = + state.currentAdGroupIndex == C.INDEX_UNSET + ? period.durationUs + : period.getAdDurationUs(state.currentAdGroupIndex, state.currentAdIndexInAdGroup); + return usToMs(periodOrAdDurationUs); + } + + private static PositionInfo getPositionInfo( + State state, + boolean useDiscontinuityPosition, + Timeline.Window window, + Timeline.Period period) { + @Nullable Object windowUid = null; + @Nullable Object periodUid = null; + int mediaItemIndex = state.currentMediaItemIndex; + int periodIndex = C.INDEX_UNSET; + @Nullable MediaItem mediaItem = null; + if (!state.timeline.isEmpty()) { + periodIndex = getCurrentPeriodIndexInternal(state, window); + periodUid = state.timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; + windowUid = state.timeline.getWindow(mediaItemIndex, window).uid; + mediaItem = window.mediaItem; + } + long contentPositionMs; + long positionMs; + if (useDiscontinuityPosition) { + positionMs = state.discontinuityPositionMs; + contentPositionMs = + state.currentAdGroupIndex == C.INDEX_UNSET + ? positionMs + : state.contentPositionMsSupplier.get(); + } else { + contentPositionMs = state.contentPositionMsSupplier.get(); + positionMs = + state.currentAdGroupIndex != C.INDEX_UNSET + ? state.adPositionMsSupplier.get() + : contentPositionMs; + } + return new PositionInfo( + windowUid, + mediaItemIndex, + mediaItem, + periodUid, + periodIndex, + positionMs, + contentPositionMs, + state.currentAdGroupIndex, + state.currentAdIndexInAdGroup); + } + + private static int getMediaItemTransitionReason( + State previousState, + State newState, + int positionDiscontinuityReason, + Timeline.Window window) { + Timeline previousTimeline = previousState.timeline; + Timeline newTimeline = newState.timeline; + if (newTimeline.isEmpty() && previousTimeline.isEmpty()) { + return C.INDEX_UNSET; + } else if (newTimeline.isEmpty() != previousTimeline.isEmpty()) { + return MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; + } + Object previousWindowUid = + previousState.timeline.getWindow(previousState.currentMediaItemIndex, window).uid; + Object newWindowUid = newState.timeline.getWindow(newState.currentMediaItemIndex, window).uid; + if (!previousWindowUid.equals(newWindowUid)) { + if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { + return MEDIA_ITEM_TRANSITION_REASON_AUTO; + } else if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK) { + return MEDIA_ITEM_TRANSITION_REASON_SEEK; + } else { + return MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; + } + } + // Only mark changes within the current item as a transition if we are repeating automatically + // or via a seek to next/previous. + if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION + && previousState.contentPositionMsSupplier.get() + > newState.contentPositionMsSupplier.get()) { + return MEDIA_ITEM_TRANSITION_REASON_REPEAT; + } + if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK + && /* TODO: mark repetition seeks to detect this case */ false) { + return MEDIA_ITEM_TRANSITION_REASON_SEEK; + } + return C.INDEX_UNSET; } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 7c9fcf1afe..0fb17455b5 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -16,15 +16,28 @@ package com.google.android.exoplayer2; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.os.Looper; +import android.os.SystemClock; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Player.Commands; import com.google.android.exoplayer2.Player.Listener; import com.google.android.exoplayer2.SimpleBasePlayer.State; +import com.google.android.exoplayer2.testutil.FakeMetadataEntry; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.CueGroup; +import com.google.android.exoplayer2.util.Size; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; @@ -35,6 +48,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.shadows.ShadowLooper; /** Unit test for {@link SimpleBasePlayer}. */ @RunWith(AndroidJUnit4.class) @@ -61,6 +75,64 @@ public class SimpleBasePlayerTest { /* playWhenReady= */ true, /* playWhenReadyChangeReason= */ Player .PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS) + .setPlaybackState(Player.STATE_IDLE) + .setPlaybackSuppressionReason( + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) + .setPlayerError( + new PlaybackException( + /* message= */ null, + /* cause= */ null, + PlaybackException.ERROR_CODE_DECODING_FAILED)) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .setShuffleModeEnabled(true) + .setIsLoading(false) + .setSeekBackIncrementMs(5000) + .setSeekForwardIncrementMs(4000) + .setMaxSeekToPreviousPositionMs(3000) + .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)) + .setTrackSelectionParameters(TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT) + .setAudioAttributes( + new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build()) + .setVolume(0.5f) + .setVideoSize(new VideoSize(/* width= */ 200, /* height= */ 400)) + .setCurrentCues( + new CueGroup( + ImmutableList.of(new Cue.Builder().setText("text").build()), + /* presentationTimeUs= */ 123)) + .setDeviceInfo( + new DeviceInfo( + DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7)) + .setIsDeviceMuted(true) + .setAudioSessionId(78) + .setSkipSilenceEnabled(true) + .setSurfaceSize(new Size(480, 360)) + .setNewlyRenderedFirstFrame(true) + .setTimedMetadata(new Metadata()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), + /* adGroupTimesUs= */ 555, + 666)) + .build())) + .build())) + .setPlaylistMetadata(new MediaMetadata.Builder().setArtist("artist").build()) + .setCurrentMediaItemIndex(1) + .setCurrentPeriodIndex(1) + .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) + .setContentPositionMs(() -> 456) + .setAdPositionMs(() -> 6678) + .setContentBufferedPositionMs(() -> 999) + .setAdBufferedPositionMs(() -> 888) + .setTotalBufferedDurationMs(() -> 567) + .setPositionDiscontinuity( + Player.DISCONTINUITY_REASON_SEEK, /* discontinuityPositionMs= */ 400) .build(); State newState = state.buildUpon().build(); @@ -70,29 +142,622 @@ public class SimpleBasePlayerTest { } @Test - public void stateBuilderSetAvailableCommands_setsAvailableCommands() { + public void playlistItemBuildUpon_build_isEqual() { + SimpleBasePlayer.PlaylistItem playlistItem = + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setTracks( + new Tracks( + ImmutableList.of( + new Tracks.Group( + new TrackGroup(new Format.Builder().build()), + /* adaptiveSupported= */ true, + /* trackSupport= */ new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true})))) + .setMediaItem(new MediaItem.Builder().setMediaId("id").build()) + .setMediaMetadata(new MediaMetadata.Builder().setTitle("title").build()) + .setManifest(new Object()) + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder().setTargetOffsetMs(2000).build()) + .setPresentationStartTimeMs(12) + .setWindowStartTimeMs(23) + .setElapsedRealtimeEpochOffsetMs(10234) + .setIsSeekable(true) + .setIsDynamic(true) + .setDefaultPositionUs(456_789) + .setDurationUs(500_000) + .setPositionInFirstPeriodUs(100_000) + .setIsPlaceholder(true) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()).build())) + .build(); + + SimpleBasePlayer.PlaylistItem newPlaylistItem = playlistItem.buildUpon().build(); + + assertThat(newPlaylistItem).isEqualTo(playlistItem); + assertThat(newPlaylistItem.hashCode()).isEqualTo(playlistItem.hashCode()); + } + + @Test + public void periodDataBuildUpon_build_isEqual() { + SimpleBasePlayer.PeriodData periodData = + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setIsPlaceholder(true) + .setDurationUs(600_000) + .setAdPlaybackState( + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + .build(); + + SimpleBasePlayer.PeriodData newPeriodData = periodData.buildUpon().build(); + + assertThat(newPeriodData).isEqualTo(periodData); + assertThat(newPeriodData.hashCode()).isEqualTo(periodData.hashCode()); + } + + @Test + public void stateBuilderBuild_setsCorrectValues() { Commands commands = new Commands.Builder() .addAll(Player.COMMAND_GET_DEVICE_VOLUME, Player.COMMAND_GET_TIMELINE) .build(); - State state = new State.Builder().setAvailableCommands(commands).build(); + PlaybackException error = + new PlaybackException( + /* message= */ null, /* cause= */ null, PlaybackException.ERROR_CODE_DECODING_FAILED); + PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 2f); + TrackSelectionParameters trackSelectionParameters = + TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT + .buildUpon() + .setMaxVideoBitrate(1000) + .build(); + AudioAttributes audioAttributes = + new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(); + VideoSize videoSize = new VideoSize(/* width= */ 200, /* height= */ 400); + CueGroup cueGroup = + new CueGroup( + ImmutableList.of(new Cue.Builder().setText("text").build()), + /* presentationTimeUs= */ 123); + Metadata timedMetadata = new Metadata(new FakeMetadataEntry("data")); + Size surfaceSize = new Size(480, 360); + DeviceInfo deviceInfo = + new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7); + ImmutableList playlist = + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + .build())) + .build()); + MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build(); + SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456; + SimpleBasePlayer.PositionSupplier adPositionSupplier = () -> 6678; + SimpleBasePlayer.PositionSupplier contentBufferedPositionSupplier = () -> 999; + SimpleBasePlayer.PositionSupplier adBufferedPositionSupplier = () -> 888; + SimpleBasePlayer.PositionSupplier totalBufferedPositionSupplier = () -> 567; - assertThat(state.availableCommands).isEqualTo(commands); - } - - @Test - public void stateBuilderSetPlayWhenReady_setsStatePlayWhenReadyAndReason() { State state = new State.Builder() + .setAvailableCommands(commands) .setPlayWhenReady( /* playWhenReady= */ true, /* playWhenReadyChangeReason= */ Player .PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS) + .setPlaybackState(Player.STATE_IDLE) + .setPlaybackSuppressionReason( + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) + .setPlayerError(error) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .setShuffleModeEnabled(true) + .setIsLoading(false) + .setSeekBackIncrementMs(5000) + .setSeekForwardIncrementMs(4000) + .setMaxSeekToPreviousPositionMs(3000) + .setPlaybackParameters(playbackParameters) + .setTrackSelectionParameters(trackSelectionParameters) + .setAudioAttributes(audioAttributes) + .setVolume(0.5f) + .setVideoSize(videoSize) + .setCurrentCues(cueGroup) + .setDeviceInfo(deviceInfo) + .setDeviceVolume(5) + .setIsDeviceMuted(true) + .setAudioSessionId(78) + .setSkipSilenceEnabled(true) + .setSurfaceSize(surfaceSize) + .setNewlyRenderedFirstFrame(true) + .setTimedMetadata(timedMetadata) + .setPlaylist(playlist) + .setPlaylistMetadata(playlistMetadata) + .setCurrentMediaItemIndex(1) + .setCurrentPeriodIndex(1) + .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) + .setContentPositionMs(contentPositionSupplier) + .setAdPositionMs(adPositionSupplier) + .setContentBufferedPositionMs(contentBufferedPositionSupplier) + .setAdBufferedPositionMs(adBufferedPositionSupplier) + .setTotalBufferedDurationMs(totalBufferedPositionSupplier) + .setPositionDiscontinuity( + Player.DISCONTINUITY_REASON_SEEK, /* discontinuityPositionMs= */ 400) .build(); + assertThat(state.availableCommands).isEqualTo(commands); assertThat(state.playWhenReady).isTrue(); assertThat(state.playWhenReadyChangeReason) .isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS); + assertThat(state.playbackState).isEqualTo(Player.STATE_IDLE); + assertThat(state.playbackSuppressionReason) + .isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); + assertThat(state.playerError).isEqualTo(error); + assertThat(state.repeatMode).isEqualTo(Player.REPEAT_MODE_ALL); + assertThat(state.shuffleModeEnabled).isTrue(); + assertThat(state.isLoading).isFalse(); + assertThat(state.seekBackIncrementMs).isEqualTo(5000); + assertThat(state.seekForwardIncrementMs).isEqualTo(4000); + assertThat(state.maxSeekToPreviousPositionMs).isEqualTo(3000); + assertThat(state.playbackParameters).isEqualTo(playbackParameters); + assertThat(state.trackSelectionParameters).isEqualTo(trackSelectionParameters); + assertThat(state.audioAttributes).isEqualTo(audioAttributes); + assertThat(state.volume).isEqualTo(0.5f); + assertThat(state.videoSize).isEqualTo(videoSize); + assertThat(state.currentCues).isEqualTo(cueGroup); + assertThat(state.deviceInfo).isEqualTo(deviceInfo); + assertThat(state.deviceVolume).isEqualTo(5); + assertThat(state.isDeviceMuted).isTrue(); + assertThat(state.audioSessionId).isEqualTo(78); + assertThat(state.skipSilenceEnabled).isTrue(); + assertThat(state.surfaceSize).isEqualTo(surfaceSize); + assertThat(state.newlyRenderedFirstFrame).isTrue(); + assertThat(state.timedMetadata).isEqualTo(timedMetadata); + assertThat(state.playlistItems).isEqualTo(playlist); + assertThat(state.playlistMetadata).isEqualTo(playlistMetadata); + assertThat(state.currentMediaItemIndex).isEqualTo(1); + assertThat(state.currentPeriodIndex).isEqualTo(1); + assertThat(state.currentAdGroupIndex).isEqualTo(1); + assertThat(state.currentAdIndexInAdGroup).isEqualTo(2); + assertThat(state.contentPositionMsSupplier).isEqualTo(contentPositionSupplier); + assertThat(state.adPositionMsSupplier).isEqualTo(adPositionSupplier); + assertThat(state.contentBufferedPositionMsSupplier).isEqualTo(contentBufferedPositionSupplier); + assertThat(state.adBufferedPositionMsSupplier).isEqualTo(adBufferedPositionSupplier); + assertThat(state.totalBufferedDurationMsSupplier).isEqualTo(totalBufferedPositionSupplier); + assertThat(state.hasPositionDiscontinuity).isTrue(); + assertThat(state.positionDiscontinuityReason).isEqualTo(Player.DISCONTINUITY_REASON_SEEK); + assertThat(state.discontinuityPositionMs).isEqualTo(400); + } + + @Test + public void stateBuilderBuild_emptyTimelineWithReadyState_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist(ImmutableList.of()) + .setPlaybackState(Player.STATE_READY) + .build()); + } + + @Test + public void stateBuilderBuild_emptyTimelineWithBufferingState_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist(ImmutableList.of()) + .setPlaybackState(Player.STATE_BUFFERING) + .build()); + } + + @Test + public void stateBuilderBuild_idleStateWithIsLoading_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaybackState(Player.STATE_IDLE) + .setIsLoading(true) + .build()); + } + + @Test + public void stateBuilderBuild_currentWindowIndexExceedsPlaylistLength_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + .setCurrentMediaItemIndex(2) + .build()); + } + + @Test + public void stateBuilderBuild_currentPeriodIndexExceedsPlaylistLength_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + .setCurrentPeriodIndex(2) + .build()); + } + + @Test + public void stateBuilderBuild_currentPeriodIndexInOtherMediaItem_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + .setCurrentMediaItemIndex(0) + .setCurrentPeriodIndex(1) + .build()); + } + + @Test + public void stateBuilderBuild_currentAdGroupIndexExceedsAdGroupCount_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), + /* adGroupTimesUs= */ 123)) + .build())) + .build())) + .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) + .build()); + } + + @Test + public void stateBuilderBuild_currentAdIndexExceedsAdCountInAdGroup_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), + /* adGroupTimesUs= */ 123) + .withAdCount( + /* adGroupIndex= */ 0, /* adCount= */ 2)) + .build())) + .build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2) + .build()); + } + + @Test + public void stateBuilderBuild_playerErrorInNonIdleState_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaybackState(Player.STATE_READY) + .setPlayerError( + new PlaybackException( + /* message= */ null, + /* cause= */ null, + PlaybackException.ERROR_CODE_DECODING_FAILED)) + .build()); + } + + @Test + public void stateBuilderBuild_multiplePlaylistItemsWithSameIds_throwsException() { + Object uid = new Object(); + + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(uid).build(), + new SimpleBasePlayer.PlaylistItem.Builder(uid).build())) + .build()); + } + + @Test + public void stateBuilderBuild_adGroupIndexWithUnsetAdIndex_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setCurrentAd(/* adGroupIndex= */ C.INDEX_UNSET, /* adIndexInAdGroup= */ 0)); + } + + @Test + public void stateBuilderBuild_unsetAdGroupIndexWithSetAdIndex_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ C.INDEX_UNSET)); + } + + @Test + public void stateBuilderBuild_unsetAdGroupIndexAndAdIndex_doesNotThrow() { + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setCurrentAd(/* adGroupIndex= */ C.INDEX_UNSET, /* adIndexInAdGroup= */ C.INDEX_UNSET) + .build(); + + assertThat(state.currentAdGroupIndex).isEqualTo(C.INDEX_UNSET); + assertThat(state.currentAdIndexInAdGroup).isEqualTo(C.INDEX_UNSET); + } + + @Test + public void stateBuilderBuild_returnsAdvancingContentPositionWhenPlaying() { + SystemClock.setCurrentTimeMillis(10000); + + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + .setContentPositionMs(4000) + .setPlayWhenReady(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) + .setPlaybackState(Player.STATE_READY) + .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)) + .build(); + long position1 = state.contentPositionMsSupplier.get(); + SystemClock.setCurrentTimeMillis(12000); + long position2 = state.contentPositionMsSupplier.get(); + + assertThat(position1).isEqualTo(4000); + assertThat(position2).isEqualTo(8000); + } + + @Test + public void stateBuilderBuild_returnsConstantContentPositionWhenNotPlaying() { + SystemClock.setCurrentTimeMillis(10000); + + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + .setContentPositionMs(4000) + .setPlaybackState(Player.STATE_BUFFERING) + .build(); + long position1 = state.contentPositionMsSupplier.get(); + SystemClock.setCurrentTimeMillis(12000); + long position2 = state.contentPositionMsSupplier.get(); + + assertThat(position1).isEqualTo(4000); + assertThat(position2).isEqualTo(4000); + } + + @Test + public void stateBuilderBuild_returnsAdvancingAdPositionWhenPlaying() { + SystemClock.setCurrentTimeMillis(10000); + + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), + /* adGroupTimesUs= */ 123) + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)) + .build())) + .build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1) + .setAdPositionMs(4000) + .setPlayWhenReady(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) + .setPlaybackState(Player.STATE_READY) + // This should be ignored as ads are assumed to be played with unit speed. + .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)) + .build(); + long position1 = state.adPositionMsSupplier.get(); + SystemClock.setCurrentTimeMillis(12000); + long position2 = state.adPositionMsSupplier.get(); + + assertThat(position1).isEqualTo(4000); + assertThat(position2).isEqualTo(6000); + } + + @Test + public void stateBuilderBuild_returnsConstantAdPositionWhenNotPlaying() { + SystemClock.setCurrentTimeMillis(10000); + + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), + /* adGroupTimesUs= */ 123) + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)) + .build())) + .build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1) + .setAdPositionMs(4000) + .setPlaybackState(Player.STATE_BUFFERING) + .build(); + long position1 = state.adPositionMsSupplier.get(); + SystemClock.setCurrentTimeMillis(12000); + long position2 = state.adPositionMsSupplier.get(); + + assertThat(position1).isEqualTo(4000); + assertThat(position2).isEqualTo(4000); + } + + @Test + public void playlistItemBuilderBuild_setsCorrectValues() { + Object uid = new Object(); + Tracks tracks = + new Tracks( + ImmutableList.of( + new Tracks.Group( + new TrackGroup(new Format.Builder().build()), + /* adaptiveSupported= */ true, + /* trackSupport= */ new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true}))); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("id").build(); + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); + Object manifest = new Object(); + MediaItem.LiveConfiguration liveConfiguration = + new MediaItem.LiveConfiguration.Builder().setTargetOffsetMs(2000).build(); + ImmutableList periods = + ImmutableList.of(new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()).build()); + + SimpleBasePlayer.PlaylistItem playlistItem = + new SimpleBasePlayer.PlaylistItem.Builder(uid) + .setTracks(tracks) + .setMediaItem(mediaItem) + .setMediaMetadata(mediaMetadata) + .setManifest(manifest) + .setLiveConfiguration(liveConfiguration) + .setPresentationStartTimeMs(12) + .setWindowStartTimeMs(23) + .setElapsedRealtimeEpochOffsetMs(10234) + .setIsSeekable(true) + .setIsDynamic(true) + .setDefaultPositionUs(456_789) + .setDurationUs(500_000) + .setPositionInFirstPeriodUs(100_000) + .setIsPlaceholder(true) + .setPeriods(periods) + .build(); + + assertThat(playlistItem.uid).isEqualTo(uid); + assertThat(playlistItem.tracks).isEqualTo(tracks); + assertThat(playlistItem.mediaItem).isEqualTo(mediaItem); + assertThat(playlistItem.mediaMetadata).isEqualTo(mediaMetadata); + assertThat(playlistItem.manifest).isEqualTo(manifest); + assertThat(playlistItem.liveConfiguration).isEqualTo(liveConfiguration); + assertThat(playlistItem.presentationStartTimeMs).isEqualTo(12); + assertThat(playlistItem.windowStartTimeMs).isEqualTo(23); + assertThat(playlistItem.elapsedRealtimeEpochOffsetMs).isEqualTo(10234); + assertThat(playlistItem.isSeekable).isTrue(); + assertThat(playlistItem.isDynamic).isTrue(); + assertThat(playlistItem.defaultPositionUs).isEqualTo(456_789); + assertThat(playlistItem.durationUs).isEqualTo(500_000); + assertThat(playlistItem.positionInFirstPeriodUs).isEqualTo(100_000); + assertThat(playlistItem.isPlaceholder).isTrue(); + assertThat(playlistItem.periods).isEqualTo(periods); + } + + @Test + public void playlistItemBuilderBuild_presentationStartTimeIfNotLive_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPresentationStartTimeMs(12) + .build()); + } + + @Test + public void playlistItemBuilderBuild_windowStartTimeIfNotLive_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setWindowStartTimeMs(12) + .build()); + } + + @Test + public void playlistItemBuilderBuild_elapsedEpochOffsetIfNotLive_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setElapsedRealtimeEpochOffsetMs(12) + .build()); + } + + @Test + public void + playlistItemBuilderBuild_windowStartTimeLessThanPresentationStartTime_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setLiveConfiguration(MediaItem.LiveConfiguration.UNSET) + .setWindowStartTimeMs(12) + .setPresentationStartTimeMs(13) + .build()); + } + + @Test + public void playlistItemBuilderBuild_multiplePeriodsWithSameUid_throwsException() { + Object uid = new Object(); + + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(uid).build(), + new SimpleBasePlayer.PeriodData.Builder(uid).build())) + .build()); + } + + @Test + public void playlistItemBuilderBuild_defaultPositionGreaterThanDuration_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setDefaultPositionUs(16) + .setDurationUs(15) + .build()); + } + + @Test + public void periodDataBuilderBuild_setsCorrectValues() { + Object uid = new Object(); + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666); + + SimpleBasePlayer.PeriodData periodData = + new SimpleBasePlayer.PeriodData.Builder(uid) + .setIsPlaceholder(true) + .setDurationUs(600_000) + .setAdPlaybackState(adPlaybackState) + .build(); + + assertThat(periodData.uid).isEqualTo(uid); + assertThat(periodData.isPlaceholder).isTrue(); + assertThat(periodData.durationUs).isEqualTo(600_000); + assertThat(periodData.adPlaybackState).isEqualTo(adPlaybackState); } @Test @@ -101,6 +766,72 @@ public class SimpleBasePlayerTest { new Commands.Builder() .addAll(Player.COMMAND_GET_DEVICE_VOLUME, Player.COMMAND_GET_TIMELINE) .build(); + PlaybackException error = + new PlaybackException( + /* message= */ null, /* cause= */ null, PlaybackException.ERROR_CODE_DECODING_FAILED); + PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 2f); + TrackSelectionParameters trackSelectionParameters = + TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT + .buildUpon() + .setMaxVideoBitrate(1000) + .build(); + AudioAttributes audioAttributes = + new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(); + VideoSize videoSize = new VideoSize(/* width= */ 200, /* height= */ 400); + CueGroup cueGroup = + new CueGroup( + ImmutableList.of(new Cue.Builder().setText("text").build()), + /* presentationTimeUs= */ 123); + DeviceInfo deviceInfo = + new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7); + MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build(); + SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456; + SimpleBasePlayer.PositionSupplier contentBufferedPositionSupplier = () -> 499; + SimpleBasePlayer.PositionSupplier totalBufferedPositionSupplier = () -> 567; + Object playlistItemUid = new Object(); + Object periodUid = new Object(); + Tracks tracks = + new Tracks( + ImmutableList.of( + new Tracks.Group( + new TrackGroup(new Format.Builder().build()), + /* adaptiveSupported= */ true, + /* trackSupport= */ new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true}))); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("id").build(); + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); + Object manifest = new Object(); + Size surfaceSize = new Size(480, 360); + MediaItem.LiveConfiguration liveConfiguration = + new MediaItem.LiveConfiguration.Builder().setTargetOffsetMs(2000).build(); + ImmutableList playlist = + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(playlistItemUid) + .setTracks(tracks) + .setMediaItem(mediaItem) + .setMediaMetadata(mediaMetadata) + .setManifest(manifest) + .setLiveConfiguration(liveConfiguration) + .setPresentationStartTimeMs(12) + .setWindowStartTimeMs(23) + .setElapsedRealtimeEpochOffsetMs(10234) + .setIsSeekable(true) + .setIsDynamic(true) + .setDefaultPositionUs(456_789) + .setDurationUs(500_000) + .setPositionInFirstPeriodUs(100_000) + .setIsPlaceholder(true) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(periodUid) + .setIsPlaceholder(true) + .setDurationUs(600_000) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + .build())) + .build()); State state = new State.Builder() .setAvailableCommands(commands) @@ -108,8 +839,38 @@ public class SimpleBasePlayerTest { /* playWhenReady= */ true, /* playWhenReadyChangeReason= */ Player .PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS) + .setPlaybackState(Player.STATE_IDLE) + .setPlaybackSuppressionReason( + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) + .setPlayerError(error) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .setShuffleModeEnabled(true) + .setIsLoading(false) + .setSeekBackIncrementMs(5000) + .setSeekForwardIncrementMs(4000) + .setMaxSeekToPreviousPositionMs(3000) + .setPlaybackParameters(playbackParameters) + .setTrackSelectionParameters(trackSelectionParameters) + .setAudioAttributes(audioAttributes) + .setVolume(0.5f) + .setVideoSize(videoSize) + .setCurrentCues(cueGroup) + .setDeviceInfo(deviceInfo) + .setDeviceVolume(5) + .setIsDeviceMuted(true) + .setAudioSessionId(78) + .setSkipSilenceEnabled(true) + .setSurfaceSize(surfaceSize) + .setPlaylist(playlist) + .setPlaylistMetadata(playlistMetadata) + .setCurrentMediaItemIndex(1) + .setCurrentPeriodIndex(1) + .setContentPositionMs(contentPositionSupplier) + .setContentBufferedPositionMs(contentBufferedPositionSupplier) + .setTotalBufferedDurationMs(totalBufferedPositionSupplier) .build(); - SimpleBasePlayer player = + + Player player = new SimpleBasePlayer(Looper.myLooper()) { @Override protected State getState() { @@ -120,11 +881,178 @@ public class SimpleBasePlayerTest { assertThat(player.getApplicationLooper()).isEqualTo(Looper.myLooper()); assertThat(player.getAvailableCommands()).isEqualTo(commands); assertThat(player.getPlayWhenReady()).isTrue(); + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + assertThat(player.getPlaybackSuppressionReason()) + .isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); + assertThat(player.getPlayerError()).isEqualTo(error); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + assertThat(player.getShuffleModeEnabled()).isTrue(); + assertThat(player.isLoading()).isFalse(); + assertThat(player.getSeekBackIncrement()).isEqualTo(5000); + assertThat(player.getSeekForwardIncrement()).isEqualTo(4000); + assertThat(player.getMaxSeekToPreviousPosition()).isEqualTo(3000); + assertThat(player.getPlaybackParameters()).isEqualTo(playbackParameters); + assertThat(player.getCurrentTracks()).isEqualTo(tracks); + assertThat(player.getTrackSelectionParameters()).isEqualTo(trackSelectionParameters); + assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata); + assertThat(player.getPlaylistMetadata()).isEqualTo(playlistMetadata); + assertThat(player.getCurrentPeriodIndex()).isEqualTo(1); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getDuration()).isEqualTo(500); + assertThat(player.getCurrentPosition()).isEqualTo(456); + assertThat(player.getBufferedPosition()).isEqualTo(499); + assertThat(player.getTotalBufferedDuration()).isEqualTo(567); + assertThat(player.isPlayingAd()).isFalse(); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(C.INDEX_UNSET); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(C.INDEX_UNSET); + assertThat(player.getContentPosition()).isEqualTo(456); + assertThat(player.getContentBufferedPosition()).isEqualTo(499); + assertThat(player.getAudioAttributes()).isEqualTo(audioAttributes); + assertThat(player.getVolume()).isEqualTo(0.5f); + assertThat(player.getVideoSize()).isEqualTo(videoSize); + assertThat(player.getCurrentCues()).isEqualTo(cueGroup); + assertThat(player.getDeviceInfo()).isEqualTo(deviceInfo); + assertThat(player.getDeviceVolume()).isEqualTo(5); + assertThat(player.isDeviceMuted()).isTrue(); + assertThat(player.getSurfaceSize()).isEqualTo(surfaceSize); + Timeline timeline = player.getCurrentTimeline(); + assertThat(timeline.getPeriodCount()).isEqualTo(2); + assertThat(timeline.getWindowCount()).isEqualTo(2); + Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()); + assertThat(window.defaultPositionUs).isEqualTo(0); + assertThat(window.durationUs).isEqualTo(C.TIME_UNSET); + assertThat(window.elapsedRealtimeEpochOffsetMs).isEqualTo(C.TIME_UNSET); + assertThat(window.firstPeriodIndex).isEqualTo(0); + assertThat(window.isDynamic).isFalse(); + assertThat(window.isPlaceholder).isFalse(); + assertThat(window.isSeekable).isFalse(); + assertThat(window.lastPeriodIndex).isEqualTo(0); + assertThat(window.positionInFirstPeriodUs).isEqualTo(0); + assertThat(window.presentationStartTimeMs).isEqualTo(C.TIME_UNSET); + assertThat(window.windowStartTimeMs).isEqualTo(C.TIME_UNSET); + assertThat(window.liveConfiguration).isNull(); + assertThat(window.manifest).isNull(); + assertThat(window.mediaItem).isEqualTo(MediaItem.EMPTY); + window = timeline.getWindow(/* windowIndex= */ 1, new Timeline.Window()); + assertThat(window.defaultPositionUs).isEqualTo(456_789); + assertThat(window.durationUs).isEqualTo(500_000); + assertThat(window.elapsedRealtimeEpochOffsetMs).isEqualTo(10234); + assertThat(window.firstPeriodIndex).isEqualTo(1); + assertThat(window.isDynamic).isTrue(); + assertThat(window.isPlaceholder).isTrue(); + assertThat(window.isSeekable).isTrue(); + assertThat(window.lastPeriodIndex).isEqualTo(1); + assertThat(window.positionInFirstPeriodUs).isEqualTo(100_000); + assertThat(window.presentationStartTimeMs).isEqualTo(12); + assertThat(window.windowStartTimeMs).isEqualTo(23); + assertThat(window.liveConfiguration).isEqualTo(liveConfiguration); + assertThat(window.manifest).isEqualTo(manifest); + assertThat(window.mediaItem).isEqualTo(mediaItem); + assertThat(window.uid).isEqualTo(playlistItemUid); + Timeline.Period period = + timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true); + assertThat(period.durationUs).isEqualTo(C.TIME_UNSET); + assertThat(period.isPlaceholder).isFalse(); + assertThat(period.positionInWindowUs).isEqualTo(0); + assertThat(period.windowIndex).isEqualTo(0); + assertThat(period.getAdGroupCount()).isEqualTo(0); + period = timeline.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true); + assertThat(period.durationUs).isEqualTo(600_000); + assertThat(period.isPlaceholder).isTrue(); + assertThat(period.positionInWindowUs).isEqualTo(-100_000); + assertThat(period.windowIndex).isEqualTo(1); + assertThat(period.id).isEqualTo(periodUid); + assertThat(period.getAdGroupCount()).isEqualTo(2); + assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 0)).isEqualTo(555); + assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 1)).isEqualTo(666); + } + + @Test + public void getterMethods_duringAd_returnAdState() { + SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456; + SimpleBasePlayer.PositionSupplier contentBufferedPositionSupplier = () -> 499; + SimpleBasePlayer.PositionSupplier totalBufferedPositionSupplier = () -> 567; + SimpleBasePlayer.PositionSupplier adPositionSupplier = () -> 321; + SimpleBasePlayer.PositionSupplier adBufferedPositionSupplier = () -> 345; + ImmutableList playlist = + ImmutableList.of( + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + .setDurationUs(500_000) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setIsPlaceholder(true) + .setDurationUs(600_000) + .setAdPlaybackState( + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666) + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) + .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1) + .withAdDurationsUs( + /* adGroupIndex= */ 0, /* adDurationsUs... */ 700_000) + .withAdDurationsUs( + /* adGroupIndex= */ 1, /* adDurationsUs... */ 800_000)) + .build())) + .build()); + State state = + new State.Builder() + .setPlaylist(playlist) + .setCurrentMediaItemIndex(1) + .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0) + .setContentPositionMs(contentPositionSupplier) + .setContentBufferedPositionMs(contentBufferedPositionSupplier) + .setTotalBufferedDurationMs(totalBufferedPositionSupplier) + .setAdPositionMs(adPositionSupplier) + .setAdBufferedPositionMs(adBufferedPositionSupplier) + .build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPeriodIndex()).isEqualTo(1); + assertThat(player.getDuration()).isEqualTo(800); + assertThat(player.getCurrentPosition()).isEqualTo(321); + assertThat(player.getBufferedPosition()).isEqualTo(345); + assertThat(player.getTotalBufferedDuration()).isEqualTo(567); + assertThat(player.isPlayingAd()).isTrue(); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(1); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getContentPosition()).isEqualTo(456); + assertThat(player.getContentBufferedPosition()).isEqualTo(499); + } + + @Test + public void getterMethods_withEmptyTimeline_returnPlaceholderValues() { + State state = new State.Builder().setCurrentMediaItemIndex(4).build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getCurrentTracks()).isEqualTo(Tracks.EMPTY); + assertThat(player.getMediaMetadata()).isEqualTo(MediaMetadata.EMPTY); + assertThat(player.getCurrentPeriodIndex()).isEqualTo(4); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(4); } @SuppressWarnings("deprecation") // Verifying deprecated listener call. @Test - public void invalidateState_updatesStateAndInformsListeners() { + public void invalidateState_updatesStateAndInformsListeners() throws Exception { + Object mediaItemUid0 = new Object(); + MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem0 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); State state1 = new State.Builder() .setAvailableCommands(new Commands.Builder().addAllCommands().build()) @@ -132,14 +1060,108 @@ public class SimpleBasePlayerTest { /* playWhenReady= */ true, /* playWhenReadyChangeReason= */ Player .PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS) + .setPlaybackState(Player.STATE_READY) + .setPlaybackSuppressionReason(Player.PLAYBACK_SUPPRESSION_REASON_NONE) + .setPlayerError(null) + .setRepeatMode(Player.REPEAT_MODE_ONE) + .setShuffleModeEnabled(false) + .setIsLoading(true) + .setSeekBackIncrementMs(7000) + .setSeekForwardIncrementMs(2000) + .setMaxSeekToPreviousPositionMs(8000) + .setPlaybackParameters(PlaybackParameters.DEFAULT) + .setTrackSelectionParameters(TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT) + .setAudioAttributes(AudioAttributes.DEFAULT) + .setVolume(1f) + .setVideoSize(VideoSize.UNKNOWN) + .setCurrentCues(CueGroup.EMPTY_TIME_ZERO) + .setDeviceInfo(DeviceInfo.UNKNOWN) + .setDeviceVolume(0) + .setIsDeviceMuted(false) + .setPlaylist(ImmutableList.of(playlistItem0)) + .setPlaylistMetadata(MediaMetadata.EMPTY) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(8_000) .build(); - Commands commands = new Commands.Builder().add(Player.COMMAND_GET_TEXT).build(); + Object mediaItemUid1 = new Object(); + MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); + Tracks tracks = + new Tracks( + ImmutableList.of( + new Tracks.Group( + new TrackGroup(new Format.Builder().build()), + /* adaptiveSupported= */ true, + /* trackSupport= */ new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true}))); + SimpleBasePlayer.PlaylistItem playlistItem1 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1) + .setMediaItem(mediaItem1) + .setMediaMetadata(mediaMetadata) + .setTracks(tracks) + .build(); + Commands commands = + new Commands.Builder() + .addAll(Player.COMMAND_GET_DEVICE_VOLUME, Player.COMMAND_GET_TIMELINE) + .build(); + PlaybackException error = + new PlaybackException( + /* message= */ null, /* cause= */ null, PlaybackException.ERROR_CODE_DECODING_FAILED); + PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 2f); + TrackSelectionParameters trackSelectionParameters = + TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT + .buildUpon() + .setMaxVideoBitrate(1000) + .build(); + AudioAttributes audioAttributes = + new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(); + VideoSize videoSize = new VideoSize(/* width= */ 200, /* height= */ 400); + CueGroup cueGroup = + new CueGroup( + ImmutableList.of(new Cue.Builder().setText("text").build()), + /* presentationTimeUs= */ 123); + Metadata timedMetadata = + new Metadata(/* presentationTimeUs= */ 42, new FakeMetadataEntry("data")); + Size surfaceSize = new Size(480, 360); + DeviceInfo deviceInfo = + new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7); + MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build(); State state2 = new State.Builder() .setAvailableCommands(commands) .setPlayWhenReady( /* playWhenReady= */ false, - /* playWhenReadyChangeReason= */ Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE) + /* playWhenReadyChangeReason= */ Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) + .setPlaybackState(Player.STATE_IDLE) + .setPlaybackSuppressionReason( + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) + .setPlayerError(error) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .setShuffleModeEnabled(true) + .setIsLoading(false) + .setSeekBackIncrementMs(5000) + .setSeekForwardIncrementMs(4000) + .setMaxSeekToPreviousPositionMs(3000) + .setPlaybackParameters(playbackParameters) + .setTrackSelectionParameters(trackSelectionParameters) + .setAudioAttributes(audioAttributes) + .setVolume(0.5f) + .setVideoSize(videoSize) + .setCurrentCues(cueGroup) + .setDeviceInfo(deviceInfo) + .setDeviceVolume(5) + .setIsDeviceMuted(true) + .setAudioSessionId(78) + .setSkipSilenceEnabled(true) + .setSurfaceSize(surfaceSize) + .setNewlyRenderedFirstFrame(true) + .setTimedMetadata(timedMetadata) + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylistMetadata(playlistMetadata) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(12_000) + .setPositionDiscontinuity( + Player.DISCONTINUITY_REASON_SEEK, /* discontinuityPositionMs= */ 11_500) .build(); AtomicBoolean returnState2 = new AtomicBoolean(); SimpleBasePlayer player = @@ -156,18 +1178,521 @@ public class SimpleBasePlayerTest { returnState2.set(true); player.invalidateState(); - - // Verify updated state. - assertThat(player.getAvailableCommands()).isEqualTo(commands); + // Verify state2 is used. assertThat(player.getPlayWhenReady()).isFalse(); - // Verify listener calls. + // Idle Looper to ensure all callbacks (including onEvents) are delivered. + ShadowLooper.idleMainLooper(); + + // Assert listener calls. verify(listener).onAvailableCommandsChanged(commands); verify(listener) .onPlayWhenReadyChanged( - /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE); + /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); verify(listener) .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE); + verify(listener).onPlaybackStateChanged(Player.STATE_IDLE); + verify(listener) + .onPlaybackSuppressionReasonChanged( + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); + verify(listener).onIsPlayingChanged(false); + verify(listener).onPlayerError(error); + verify(listener).onPlayerErrorChanged(error); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verify(listener).onShuffleModeEnabledChanged(true); + verify(listener).onLoadingChanged(false); + verify(listener).onIsLoadingChanged(false); + verify(listener).onSeekBackIncrementChanged(5000); + verify(listener).onSeekForwardIncrementChanged(4000); + verify(listener).onMaxSeekToPreviousPositionChanged(3000); + verify(listener).onPlaybackParametersChanged(playbackParameters); + verify(listener).onTrackSelectionParametersChanged(trackSelectionParameters); + verify(listener).onAudioAttributesChanged(audioAttributes); + verify(listener).onVolumeChanged(0.5f); + verify(listener).onVideoSizeChanged(videoSize); + verify(listener).onCues(cueGroup.cues); + verify(listener).onCues(cueGroup); + verify(listener).onDeviceInfoChanged(deviceInfo); + verify(listener).onDeviceVolumeChanged(/* volume= */ 5, /* muted= */ true); + verify(listener) + .onTimelineChanged(state2.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onMediaMetadataChanged(mediaMetadata); + verify(listener).onTracksChanged(tracks); + verify(listener).onPlaylistMetadataChanged(playlistMetadata); + verify(listener).onAudioSessionIdChanged(78); + verify(listener).onRenderedFirstFrame(); + verify(listener).onMetadata(timedMetadata); + verify(listener).onSurfaceSizeChanged(surfaceSize.getWidth(), surfaceSize.getHeight()); + verify(listener).onSkipSilenceEnabledChanged(true); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid0, + /* mediaItemIndex= */ 0, + mediaItem0, + /* periodUid= */ mediaItemUid0, + /* periodIndex= */ 0, + /* positionMs= */ 8_000, + /* contentPositionMs= */ 8_000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid1, + /* mediaItemIndex= */ 1, + mediaItem1, + /* periodUid= */ mediaItemUid1, + /* periodIndex= */ 1, + /* positionMs= */ 11_500, + /* contentPositionMs= */ 11_500, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onMediaItemTransition(mediaItem1, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener) + .onEvents( + player, + new Player.Events( + new FlagSet.Builder() + .addAll( + Player.EVENT_TIMELINE_CHANGED, + Player.EVENT_MEDIA_ITEM_TRANSITION, + Player.EVENT_TRACKS_CHANGED, + Player.EVENT_IS_LOADING_CHANGED, + Player.EVENT_PLAYBACK_STATE_CHANGED, + Player.EVENT_PLAY_WHEN_READY_CHANGED, + Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, + Player.EVENT_IS_PLAYING_CHANGED, + Player.EVENT_REPEAT_MODE_CHANGED, + Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, + Player.EVENT_PLAYER_ERROR, + Player.EVENT_POSITION_DISCONTINUITY, + Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, + Player.EVENT_AVAILABLE_COMMANDS_CHANGED, + Player.EVENT_MEDIA_METADATA_CHANGED, + Player.EVENT_PLAYLIST_METADATA_CHANGED, + Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, + Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, + Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, + Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, + Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, + Player.EVENT_AUDIO_SESSION_ID, + Player.EVENT_VOLUME_CHANGED, + Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, + Player.EVENT_SURFACE_SIZE_CHANGED, + Player.EVENT_VIDEO_SIZE_CHANGED, + Player.EVENT_RENDERED_FIRST_FRAME, + Player.EVENT_CUES, + Player.EVENT_METADATA, + Player.EVENT_DEVICE_INFO_CHANGED, + Player.EVENT_DEVICE_VOLUME_CHANGED) + .build())); verifyNoMoreInteractions(listener); + // Assert that we actually called all listeners. + for (Method method : Player.Listener.class.getDeclaredMethods()) { + if (method.getName().equals("onSeekProcessed")) { + continue; + } + method.invoke(verify(listener), getAnyArguments(method)); + } + } + + @Test + public void invalidateState_withPlaylistItemDetailChange_reportsTimelineSourceUpdate() { + Object mediaItemUid0 = new Object(); + SimpleBasePlayer.PlaylistItem playlistItem0 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).build(); + Object mediaItemUid1 = new Object(); + SimpleBasePlayer.PlaylistItem playlistItem1 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).build(); + State state1 = + new State.Builder().setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)).build(); + SimpleBasePlayer.PlaylistItem playlistItem1Updated = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setDurationUs(10_000).build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1Updated)) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onTimelineChanged(state2.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + } + + @Test + public void invalidateState_withCurrentMediaItemRemoval_reportsDiscontinuityReasonRemoved() { + Object mediaItemUid0 = new Object(); + MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem0 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); + Object mediaItemUid1 = new Object(); + MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); + SimpleBasePlayer.PlaylistItem playlistItem1 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(5000) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(2000) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid1, + /* mediaItemIndex= */ 1, + mediaItem1, + /* periodUid= */ mediaItemUid1, + /* periodIndex= */ 1, + /* positionMs= */ 5000, + /* contentPositionMs= */ 5000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid0, + /* mediaItemIndex= */ 0, + mediaItem0, + /* periodUid= */ mediaItemUid0, + /* periodIndex= */ 0, + /* positionMs= */ 2000, + /* contentPositionMs= */ 2000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition(mediaItem0, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + } + + @Test + public void + invalidateState_withTransitionFromEndOfItem_reportsDiscontinuityReasonAutoTransition() { + Object mediaItemUid0 = new Object(); + MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem0 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0) + .setMediaItem(mediaItem0) + .setDurationUs(50_000) + .build(); + Object mediaItemUid1 = new Object(); + MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); + SimpleBasePlayer.PlaylistItem playlistItem1 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(50) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(10) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid0, + /* mediaItemIndex= */ 0, + mediaItem0, + /* periodUid= */ mediaItemUid0, + /* periodIndex= */ 0, + /* positionMs= */ 50, + /* contentPositionMs= */ 50, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid1, + /* mediaItemIndex= */ 1, + mediaItem1, + /* periodUid= */ mediaItemUid1, + /* periodIndex= */ 1, + /* positionMs= */ 10, + /* contentPositionMs= */ 10, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + verify(listener).onMediaItemTransition(mediaItem1, Player.MEDIA_ITEM_TRANSITION_REASON_AUTO); + } + + @Test + public void invalidateState_withTransitionFromMiddleOfItem_reportsDiscontinuityReasonSkip() { + Object mediaItemUid0 = new Object(); + MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem0 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0) + .setMediaItem(mediaItem0) + .setDurationUs(50_000) + .build(); + Object mediaItemUid1 = new Object(); + MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); + SimpleBasePlayer.PlaylistItem playlistItem1 = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(20) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(10) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid0, + /* mediaItemIndex= */ 0, + mediaItem0, + /* periodUid= */ mediaItemUid0, + /* periodIndex= */ 0, + /* positionMs= */ 20, + /* contentPositionMs= */ 20, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid1, + /* mediaItemIndex= */ 1, + mediaItem1, + /* periodUid= */ mediaItemUid1, + /* periodIndex= */ 1, + /* positionMs= */ 10, + /* contentPositionMs= */ 10, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_SKIP); + verify(listener) + .onMediaItemTransition(mediaItem1, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + } + + @Test + public void invalidateState_withRepeatingItem_reportsDiscontinuityReasonAutoTransition() { + Object mediaItemUid = new Object(); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid) + .setMediaItem(mediaItem) + .setDurationUs(5_000_000) + .build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(5_000) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(0) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid, + /* mediaItemIndex= */ 0, + mediaItem, + /* periodUid= */ mediaItemUid, + /* periodIndex= */ 0, + /* positionMs= */ 5_000, + /* contentPositionMs= */ 5_000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid, + /* mediaItemIndex= */ 0, + mediaItem, + /* periodUid= */ mediaItemUid, + /* periodIndex= */ 0, + /* positionMs= */ 0, + /* contentPositionMs= */ 0, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + verify(listener).onMediaItemTransition(mediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT); + } + + @Test + public void invalidateState_withDiscontinuityInsideItem_reportsDiscontinuityReasonInternal() { + Object mediaItemUid = new Object(); + MediaItem mediaItem = new MediaItem.Builder().setMediaId("0").build(); + SimpleBasePlayer.PlaylistItem playlistItem = + new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid) + .setMediaItem(mediaItem) + .setDurationUs(5_000_000) + .build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(1_000) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(3_000) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener) + .onPositionDiscontinuity( + /* oldPosition= */ new Player.PositionInfo( + mediaItemUid, + /* mediaItemIndex= */ 0, + mediaItem, + /* periodUid= */ mediaItemUid, + /* periodIndex= */ 0, + /* positionMs= */ 1_000, + /* contentPositionMs= */ 1_000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + /* newPosition= */ new Player.PositionInfo( + mediaItemUid, + /* mediaItemIndex= */ 0, + mediaItem, + /* periodUid= */ mediaItemUid, + /* periodIndex= */ 0, + /* positionMs= */ 3_000, + /* contentPositionMs= */ 3_000, + /* adGroupIndex= */ C.INDEX_UNSET, + /* adIndexInAdGroup= */ C.INDEX_UNSET), + Player.DISCONTINUITY_REASON_INTERNAL); + verify(listener, never()).onMediaItemTransition(any(), anyInt()); + } + + @Test + public void invalidateState_withMinorPositionDrift_doesNotReportsDiscontinuity() { + SimpleBasePlayer.PlaylistItem playlistItem = + new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(); + State state1 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(1_000) + .build(); + State state2 = + new State.Builder() + .setPlaylist(ImmutableList.of(playlistItem)) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(1_500) + .build(); + AtomicBoolean returnState2 = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return returnState2.get() ? state2 : state1; + } + }; + player.invalidateState(); + Listener listener = mock(Listener.class); + player.addListener(listener); + + returnState2.set(true); + player.invalidateState(); + + // Assert listener call. + verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + verify(listener, never()).onMediaItemTransition(any(), anyInt()); } @Test @@ -403,4 +1928,23 @@ public class SimpleBasePlayerTest { assertThat(callForwarded.get()).isFalse(); } + + private static Object[] getAnyArguments(Method method) { + Object[] arguments = new Object[method.getParameterCount()]; + Class[] argumentTypes = method.getParameterTypes(); + for (int i = 0; i < arguments.length; i++) { + if (argumentTypes[i].equals(Integer.TYPE)) { + arguments[i] = anyInt(); + } else if (argumentTypes[i].equals(Long.TYPE)) { + arguments[i] = anyLong(); + } else if (argumentTypes[i].equals(Float.TYPE)) { + arguments[i] = anyFloat(); + } else if (argumentTypes[i].equals(Boolean.TYPE)) { + arguments[i] = anyBoolean(); + } else { + arguments[i] = any(); + } + } + return arguments; + } } From 87b1c30d758948b4181c2411d39527e89383f0c6 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 21 Nov 2022 17:34:53 +0000 Subject: [PATCH 009/104] Add `set -eu` to all shell scripts These flags ensure that any errors cause the script to exit (instead of just carrying on) (`-e`) and that any unrecognised substitution variables cause an error instead of silently resolving to an empty string (`-u`). Issues like Issue: google/ExoPlayer#10791 should be more quickly resolved with `set -e` because the script will clearly fail with an error like `make: command not found` which would give the user a clear pointer towards the cause of the problem. #minor-release PiperOrigin-RevId: 490001419 (cherry picked from commit f83441974411dca5673a34548b11bc6e6cb809ff) --- extensions/ffmpeg/src/main/jni/build_ffmpeg.sh | 1 + extensions/opus/src/main/jni/convert_android_asm.sh | 2 +- extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh b/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh index fef653bf6e..1583c1c964 100755 --- a/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh +++ b/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +set -eu FFMPEG_MODULE_PATH=$1 NDK_PATH=$2 diff --git a/extensions/opus/src/main/jni/convert_android_asm.sh b/extensions/opus/src/main/jni/convert_android_asm.sh index 9c79738439..48b141dca2 100755 --- a/extensions/opus/src/main/jni/convert_android_asm.sh +++ b/extensions/opus/src/main/jni/convert_android_asm.sh @@ -15,7 +15,7 @@ # limitations under the License. # -set -e +set -eu ASM_CONVERTER="./libopus/celt/arm/arm2gnu.pl" if [[ ! -x "${ASM_CONVERTER}" ]]; then diff --git a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh index 18f1dd5c69..b121886070 100755 --- a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh +++ b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh @@ -18,7 +18,7 @@ # a bash script that generates the necessary config files for libvpx android ndk # builds. -set -e +set -eu if [ $# -ne 0 ]; then echo "Usage: ${0}" From dda5f9526f52f6d5824bf244c4d46f9024a480ff Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Mon, 21 Nov 2022 18:20:48 +0000 Subject: [PATCH 010/104] Fixed missing imports for Metadata and AdPlaybackState for Exoplayer PiperOrigin-RevId: 490012573 (cherry picked from commit 1551bea3983196272d1a8e836103a77fd9090568) --- .../java/com/google/android/exoplayer2/SimpleBasePlayer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index a962b8220b..aa904e591c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -32,6 +32,8 @@ import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.util.Clock; From fbab7de15ec6ef536b1546c55de790cc8c6ee8ba Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Tue, 22 Nov 2022 09:41:40 +0000 Subject: [PATCH 011/104] Fixed Exoplayer imports for SimpleBasePlayerTest PiperOrigin-RevId: 490181547 (cherry picked from commit 788f74740b2e560af255ab948a4bc64d116633ca) --- .../google/android/exoplayer2/SimpleBasePlayerTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 0fb17455b5..41e49bd0ab 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -33,10 +33,17 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Player.Commands; import com.google.android.exoplayer2.Player.Listener; import com.google.android.exoplayer2.SimpleBasePlayer.State; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.FakeMetadataEntry; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.CueGroup; +import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; +import com.google.android.exoplayer2.util.FlagSet; import com.google.android.exoplayer2.util.Size; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; From f5276323c6973ffa6031d9c5b9f15cb05f46ef29 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 22 Nov 2022 13:09:17 +0000 Subject: [PATCH 012/104] Add `DefaultExtractorsFactory.setTsSubtitleFormats` ExoPlayer is unable to detect the presence of subtitle tracks in some MPEG-TS files that don't fully declare them. It's possible for a developer to provide the list instead, but doing so is quite awkward without this helper method. This is consistent for how `DefaultExtractorsFactory` allows other aspects of the delegate `Extractor` implementations to be customised. * Issue: google/ExoPlayer#10175 * Issue: google/ExoPlayer#10505 #minor-release PiperOrigin-RevId: 490214619 (cherry picked from commit 4853444f0dc90163c257d0e20962604510557df4) --- docs/troubleshooting.md | 29 ++++++++++++++++++- .../extractor/DefaultExtractorsFactory.java | 25 +++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f93fcd020c..5ced4e270d 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -11,6 +11,7 @@ redirect_from: * [Why is seeking inaccurate in some MP3 files?][] * [Why is seeking in my video slow?][] * [Why do some MPEG-TS files fail to play?][] +* [Why are subtitles not found in some MPEG-TS files?][] * [Why do some MP4/FMP4 files play incorrectly?][] * [Why do some streams fail with HTTP response code 301 or 302?][] * [Why do some streams fail with UnrecognizedInputFormatException?][] @@ -20,7 +21,7 @@ redirect_from: * [How can I query whether the stream being played is a live stream?][] * [How do I keep audio playing when my app is backgrounded?][] * [Why does ExoPlayer support my content but the Cast extension doesn't?][] -* [Why does content fail to play, but no error is surfaced?] +* [Why does content fail to play, but no error is surfaced?][] * [How can I get a decoding extension to load and be used for playback?][] * [Can I play YouTube videos directly with ExoPlayer?][] * [Video playback is stuttering][] @@ -145,6 +146,31 @@ computationally expensive relative to AUD based frame boundary detection. Use of `FLAG_ALLOW_NON_IDR_KEYFRAMES` may result in temporary visual corruption at the start of playback and immediately after seeks when playing some MPEG-TS files. +#### Why are subtitles not found in some MPEG-TS files? #### + +Some MPEG-TS files include CEA-608 tracks but don't declare them in the +container metadata, so ExoPlayer is unable to detect them. You can manually +specify that the subtitle track(s) exist by providing a list of expected +subtitle formats to the `DefaultExtractorsFactory`, including the accessibility +channels that can be used to identify them in the MPEG-TS stream: + +```java +DefaultExtractorsFactory extractorsFactory = + new DefaultExtractorsFactory() + .setTsSubtitleFormats( + ImmutableList.of( + new Format.Builder() + .setSampleMimeType(MimeTypes.APPLICATION_CEA608) + .setAccessibilityChannel(accessibilityChannel) + // Set other subtitle format info, e.g. language. + .build())); +Player player = + new ExoPlayer.Builder( + context, + new DefaultMediaSourceFactory(context, extractorsFactory)) + .build(); +``` + #### Why do some MP4/FMP4 files play incorrectly? #### Some MP4/FMP4 files contain edit lists that rewrite the media timeline by @@ -333,6 +359,7 @@ particularly when playing DRM protected or high frame rate content, you can try [Why is seeking inaccurate in some MP3 files?]: #why-is-seeking-inaccurate-in-some-mp3-files [Why is seeking in my video slow?]: #why-is-seeking-in-my-video-slow [Why do some MPEG-TS files fail to play?]: #why-do-some-mpeg-ts-files-fail-to-play +[Why are subtitles not found in some MPEG-TS files?]: #why-are-subtitles-not-found-in-some-mpeg-ts-files [Why do some MP4/FMP4 files play incorrectly?]: #why-do-some-mp4fmp4-files-play-incorrectly [Why do some streams fail with HTTP response code 301 or 302?]: #why-do-some-streams-fail-with-http-response-code-301-or-302 [Why do some streams fail with UnrecognizedInputFormatException?]: #why-do-some-streams-fail-with-unrecognizedinputformatexception diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 7d15ad1388..66bab7db62 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri; import android.net.Uri; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.extractor.amr.AmrExtractor; @@ -43,6 +44,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader; import com.google.android.exoplayer2.extractor.wav.WavExtractor; import com.google.android.exoplayer2.util.FileTypes; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -126,11 +128,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { private @Mp3Extractor.Flags int mp3Flags; private @TsExtractor.Mode int tsMode; private @DefaultTsPayloadReaderFactory.Flags int tsFlags; + private ImmutableList tsSubtitleFormats; private int tsTimestampSearchBytes; public DefaultExtractorsFactory() { tsMode = TsExtractor.MODE_SINGLE_PMT; tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES; + tsSubtitleFormats = ImmutableList.of(); } /** @@ -301,6 +305,20 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { return this; } + /** + * Sets a list of subtitle formats to pass to the {@link DefaultTsPayloadReaderFactory} used by + * {@link TsExtractor} instances created by the factory. + * + * @see DefaultTsPayloadReaderFactory#DefaultTsPayloadReaderFactory(int, List) + * @param subtitleFormats The subtitle formats. + * @return The factory, for convenience. + */ + @CanIgnoreReturnValue + public synchronized DefaultExtractorsFactory setTsSubtitleFormats(List subtitleFormats) { + tsSubtitleFormats = ImmutableList.copyOf(subtitleFormats); + return this; + } + /** * Sets the number of bytes searched to find a timestamp for {@link TsExtractor} instances created * by the factory. @@ -414,7 +432,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { extractors.add(new PsExtractor()); break; case FileTypes.TS: - extractors.add(new TsExtractor(tsMode, tsFlags, tsTimestampSearchBytes)); + extractors.add( + new TsExtractor( + tsMode, + new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory(tsFlags, tsSubtitleFormats), + tsTimestampSearchBytes)); break; case FileTypes.WAV: extractors.add(new WavExtractor()); From 4a469358b30b5b6f7f64c7a3c0e3a3e026bfa292 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 22 Nov 2022 14:16:35 +0000 Subject: [PATCH 013/104] Reorder some release notes in other sections. PiperOrigin-RevId: 490224795 (cherry picked from commit e567594cf7640bd96ec13d071c4523f7898b55be) --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3bc2239e6c..226b18ba20 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -27,6 +27,7 @@ This release corresponds to the * Build: * Enforce minimum `compileSdkVersion` to avoid compilation errors ([#10684](https://github.com/google/ExoPlayer/issues/10684)). + * Avoid publishing block when included in another gradle build. * Track selection: * Prefer other tracks to Dolby Vision if display does not support it. ([#8944](https://github.com/google/ExoPlayer/issues/8944)). From 989e2f71d36ee66f070e60401f3455b0d3f04d87 Mon Sep 17 00:00:00 2001 From: Ian Baker Date: Thu, 24 Nov 2022 14:47:25 +0000 Subject: [PATCH 014/104] Merge pull request #10786 from TiVo:p-aacutil-test-impl PiperOrigin-RevId: 490465182 (cherry picked from commit 8a9a66c288d5ee2749a29de133e598f326e518dd) --- .../android/exoplayer2/audio/AacUtil.java | 7 +- .../android/exoplayer2/audio/AacUtilTest.java | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 library/extractor/src/test/java/com/google/android/exoplayer2/audio/AacUtilTest.java diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java index 7010f1c3bf..f787a3959d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java @@ -330,11 +330,16 @@ public final class AacUtil { int samplingFrequency; int frequencyIndex = bitArray.readBits(4); if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) { + if (bitArray.bitsLeft() < 24) { + throw ParserException.createForMalformedContainer( + /* message= */ "AAC header insufficient data", /* cause= */ null); + } samplingFrequency = bitArray.readBits(24); } else if (frequencyIndex < 13) { samplingFrequency = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; } else { - throw ParserException.createForMalformedContainer(/* message= */ null, /* cause= */ null); + throw ParserException.createForMalformedContainer( + /* message= */ "AAC header wrong Sampling Frequency Index", /* cause= */ null); } return samplingFrequency; } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/AacUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/AacUtilTest.java new file mode 100644 index 0000000000..9f5db59dff --- /dev/null +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/AacUtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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.audio; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.util.Util; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link AacUtil}. */ +@RunWith(AndroidJUnit4.class) +public final class AacUtilTest { + private static final byte[] AAC_48K_2CH_HEADER = Util.getBytesFromHexString("1190"); + + private static final byte[] NOT_ENOUGH_ARBITRARY_SAMPLING_FREQ_BITS_HEADER = + Util.getBytesFromHexString("1790"); + + private static final byte[] ARBITRARY_SAMPLING_FREQ_BITS_HEADER = + Util.getBytesFromHexString("1780000790"); + + @Test + public void parseAudioSpecificConfig_twoCh48kAac_parsedCorrectly() throws Exception { + AacUtil.Config aac = AacUtil.parseAudioSpecificConfig(AAC_48K_2CH_HEADER); + + assertThat(aac.channelCount).isEqualTo(2); + assertThat(aac.sampleRateHz).isEqualTo(48000); + assertThat(aac.codecs).isEqualTo("mp4a.40.2"); + } + + @Test + public void parseAudioSpecificConfig_arbitrarySamplingFreqHeader_parsedCorrectly() + throws Exception { + AacUtil.Config aac = AacUtil.parseAudioSpecificConfig(ARBITRARY_SAMPLING_FREQ_BITS_HEADER); + assertThat(aac.channelCount).isEqualTo(2); + assertThat(aac.sampleRateHz).isEqualTo(15); + assertThat(aac.codecs).isEqualTo("mp4a.40.2"); + } + + @Test + public void + parseAudioSpecificConfig_arbitrarySamplingFreqHeaderNotEnoughBits_throwsParserException() { + // ISO 14496-3 1.6.2.1 allows for setting of arbitrary sampling frequency, but if the extra + // frequency bits are missing, make sure the code will throw an exception. + assertThrows( + ParserException.class, + () -> AacUtil.parseAudioSpecificConfig(NOT_ENOUGH_ARBITRARY_SAMPLING_FREQ_BITS_HEADER)); + } +} From 20e54a829debdad34c5959a69fb84891ba4fe408 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 23 Nov 2022 14:10:08 +0000 Subject: [PATCH 015/104] Exclude tracks from `PlayerInfo` if not changed This change includes a change in the `IMediaController.aidl` file and needs to provide backwards compatibility for when a client connects that is of an older or newer version of the current service implementation. This CL proposes to create a new AIDL method `onPlayerInfoChangedWithExtensions` that is easier to extend in the future because it does use an `Bundle` rather than primitives. A `Bundle` can be changed in a backward/forwards compatible way in case we need further changes. The compatibility handling is provided in `MediaSessionStub` and `MediaControllerStub`. The approach is not based on specific AIDL/Binder features but implemented fully in application code. Issue: androidx/media#102 #minor-release PiperOrigin-RevId: 490483068 (cherry picked from commit f262e9132b32ad89f65853086f30389925b61422) --- .../src/main/java/com/google/android/exoplayer2/Player.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index a8fa9873f6..f26e033ce4 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -674,7 +674,8 @@ public interface Player { * to the current {@link #getRepeatMode() repeat mode}. * *

    Note that this callback is also called when the playlist becomes non-empty or empty as a - * consequence of a playlist change. + * consequence of a playlist change or {@linkplain #onAvailableCommandsChanged(Commands) a + * change in available commands}. * *

    {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. From 35a900a6e68356266137a6afd416e8a43a28118c Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 23 Nov 2022 17:56:50 +0000 Subject: [PATCH 016/104] Parse and set `peakBitrate` for Dolby TrueHD(AC-3) and (E-)AC-3 #minor-release PiperOrigin-RevId: 490527831 (cherry picked from commit 01eddb34f555903c576c79b02a7b34a53e0272cb) --- .../com/google/android/exoplayer2/audio/Ac3Util.java | 10 +++++++++- .../assets/extractordumps/mp4/sample_ac3.mp4.0.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.1.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.2.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.3.dump | 2 ++ .../mp4/sample_ac3.mp4.unknown_length.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.0.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.1.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.2.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.3.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.unknown_length.dump | 2 ++ .../assets/extractordumps/mp4/sample_eac3.mp4.0.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.1.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.2.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.3.dump | 1 + .../mp4/sample_eac3.mp4.unknown_length.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.0.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.1.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.2.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.3.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.unknown_length.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.0.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.1.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.2.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.3.dump | 1 + .../mp4/sample_eac3joc.mp4.unknown_length.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.0.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.1.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.2.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.3.dump | 1 + .../sample_eac3joc_fragmented.mp4.unknown_length.dump | 1 + 31 files changed, 49 insertions(+), 1 deletion(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index be79879dba..6a5bfd8559 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -156,6 +156,9 @@ public final class Ac3Util { if ((nextByte & 0x04) != 0) { // lfeon channelCount++; } + // bit_rate_code - 5 bits. 2 bits from previous byte and 3 bits from next. + int halfFrmsizecod = ((nextByte & 0x03) << 3) | ((data.readUnsignedByte() & 0xE0) >> 5); + int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; return new Format.Builder() .setId(trackId) .setSampleMimeType(MimeTypes.AUDIO_AC3) @@ -163,6 +166,8 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) + .setAverageBitrate(constantBitrate) + .setPeakBitrate(constantBitrate) .build(); } @@ -178,7 +183,9 @@ public final class Ac3Util { */ public static Format parseEAc3AnnexFFormat( ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { - data.skipBytes(2); // data_rate, num_ind_sub + // 13 bits for data_rate, 3 bits for num_ind_sub which are ignored. + int peakBitrate = + ((data.readUnsignedByte() & 0xFF) << 5) | ((data.readUnsignedByte() & 0xF8) >> 3); // Read the first independent substream. int fscod = (data.readUnsignedByte() & 0xC0) >> 6; @@ -214,6 +221,7 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) + .setPeakBitrate(peakBitrate) .build(); } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump index c2e51faaef..71eed666b7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump index 80f0790cd0..a6fbd97784 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 9216 sample count = 6 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump index a8d1588940..e02699e2de 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 4608 sample count = 3 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump index 17bf79c850..4b7e17e7c9 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump index c2e51faaef..71eed666b7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump index 3724592554..84217c2e01 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump index e9019d4ab1..1edd06253f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 10752 sample count = 7 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump index 2b9cb1cd52..01fd6af916 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 6144 sample count = 4 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump index eb313f941d..c303da0e15 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump index 3724592554..84217c2e01 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index 8000864576..aba5268ea2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index 49ab3da0aa..ac03cfd484 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 144000 sample count = 36 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 19bfc7c5fa..1a61f528ac 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 72000 sample count = 18 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index d34514d8a8..431599a9be 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index 8000864576..aba5268ea2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index a7f3c63f8d..6da60d472a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index a627d00633..646dd35d91 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 148000 sample count = 37 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index 31013410b6..a7ba576bf5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 76000 sample count = 19 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index 13ff558eaa..280d6febc4 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index a7f3c63f8d..6da60d472a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index ecc28b7208..c98e27dc19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index d9ed0c417d..9c9cee29df 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 741d5199ea..85c07f6d2d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index 98fe8c793d..56387fb3c7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index ecc28b7208..c98e27dc19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index c5902f5d19..c73a6282e8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index 8fa0cbf7fe..78b392053e 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index 603ca0de80..2558363342 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index cd42dac917..084d2aa030 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index c5902f5d19..c73a6282e8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 From 2910117ec141e89b0b3c36c0ac066deaf342428e Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 23 Nov 2022 21:15:09 +0000 Subject: [PATCH 017/104] Rollback of https://github.com/google/ExoPlayer/commit/01eddb34f555903c576c79b02a7b34a53e0272cb *** Original commit *** Parse and set `peakBitrate` for Dolby TrueHD(AC-3) and (E-)AC-3 #minor-release *** PiperOrigin-RevId: 490570517 (cherry picked from commit ea3552c1a030d8b14c0cd0093f7eaa6242f969eb) --- .../com/google/android/exoplayer2/audio/Ac3Util.java | 10 +--------- .../assets/extractordumps/mp4/sample_ac3.mp4.0.dump | 2 -- .../assets/extractordumps/mp4/sample_ac3.mp4.1.dump | 2 -- .../assets/extractordumps/mp4/sample_ac3.mp4.2.dump | 2 -- .../assets/extractordumps/mp4/sample_ac3.mp4.3.dump | 2 -- .../mp4/sample_ac3.mp4.unknown_length.dump | 2 -- .../mp4/sample_ac3_fragmented.mp4.0.dump | 2 -- .../mp4/sample_ac3_fragmented.mp4.1.dump | 2 -- .../mp4/sample_ac3_fragmented.mp4.2.dump | 2 -- .../mp4/sample_ac3_fragmented.mp4.3.dump | 2 -- .../mp4/sample_ac3_fragmented.mp4.unknown_length.dump | 2 -- .../assets/extractordumps/mp4/sample_eac3.mp4.0.dump | 1 - .../assets/extractordumps/mp4/sample_eac3.mp4.1.dump | 1 - .../assets/extractordumps/mp4/sample_eac3.mp4.2.dump | 1 - .../assets/extractordumps/mp4/sample_eac3.mp4.3.dump | 1 - .../mp4/sample_eac3.mp4.unknown_length.dump | 1 - .../mp4/sample_eac3_fragmented.mp4.0.dump | 1 - .../mp4/sample_eac3_fragmented.mp4.1.dump | 1 - .../mp4/sample_eac3_fragmented.mp4.2.dump | 1 - .../mp4/sample_eac3_fragmented.mp4.3.dump | 1 - .../mp4/sample_eac3_fragmented.mp4.unknown_length.dump | 1 - .../extractordumps/mp4/sample_eac3joc.mp4.0.dump | 1 - .../extractordumps/mp4/sample_eac3joc.mp4.1.dump | 1 - .../extractordumps/mp4/sample_eac3joc.mp4.2.dump | 1 - .../extractordumps/mp4/sample_eac3joc.mp4.3.dump | 1 - .../mp4/sample_eac3joc.mp4.unknown_length.dump | 1 - .../mp4/sample_eac3joc_fragmented.mp4.0.dump | 1 - .../mp4/sample_eac3joc_fragmented.mp4.1.dump | 1 - .../mp4/sample_eac3joc_fragmented.mp4.2.dump | 1 - .../mp4/sample_eac3joc_fragmented.mp4.3.dump | 1 - .../sample_eac3joc_fragmented.mp4.unknown_length.dump | 1 - 31 files changed, 1 insertion(+), 49 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 6a5bfd8559..be79879dba 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -156,9 +156,6 @@ public final class Ac3Util { if ((nextByte & 0x04) != 0) { // lfeon channelCount++; } - // bit_rate_code - 5 bits. 2 bits from previous byte and 3 bits from next. - int halfFrmsizecod = ((nextByte & 0x03) << 3) | ((data.readUnsignedByte() & 0xE0) >> 5); - int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; return new Format.Builder() .setId(trackId) .setSampleMimeType(MimeTypes.AUDIO_AC3) @@ -166,8 +163,6 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) - .setAverageBitrate(constantBitrate) - .setPeakBitrate(constantBitrate) .build(); } @@ -183,9 +178,7 @@ public final class Ac3Util { */ public static Format parseEAc3AnnexFFormat( ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { - // 13 bits for data_rate, 3 bits for num_ind_sub which are ignored. - int peakBitrate = - ((data.readUnsignedByte() & 0xFF) << 5) | ((data.readUnsignedByte() & 0xF8) >> 3); + data.skipBytes(2); // data_rate, num_ind_sub // Read the first independent substream. int fscod = (data.readUnsignedByte() & 0xC0) >> 6; @@ -221,7 +214,6 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) - .setPeakBitrate(peakBitrate) .build(); } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump index 71eed666b7..c2e51faaef 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump index a6fbd97784..80f0790cd0 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 9216 sample count = 6 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump index e02699e2de..a8d1588940 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 4608 sample count = 3 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump index 4b7e17e7c9..17bf79c850 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 1536 sample count = 1 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump index 71eed666b7..c2e51faaef 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump index 84217c2e01..3724592554 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump index 1edd06253f..e9019d4ab1 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 10752 sample count = 7 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump index 01fd6af916..2b9cb1cd52 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 6144 sample count = 4 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump index c303da0e15..eb313f941d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 1536 sample count = 1 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump index 84217c2e01..3724592554 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump @@ -10,8 +10,6 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index aba5268ea2..8000864576 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index ac03cfd484..49ab3da0aa 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 144000 sample count = 36 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 1a61f528ac..19bfc7c5fa 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 72000 sample count = 18 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index 431599a9be..d34514d8a8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 4000 sample count = 1 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index aba5268ea2..8000864576 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index 6da60d472a..a7f3c63f8d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index 646dd35d91..a627d00633 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 148000 sample count = 37 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index a7ba576bf5..31013410b6 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 76000 sample count = 19 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index 280d6febc4..13ff558eaa 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 4000 sample count = 1 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index 6da60d472a..a7f3c63f8d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index c98e27dc19..ecc28b7208 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index 9c9cee29df..d9ed0c417d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 110080 sample count = 43 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 85c07f6d2d..741d5199ea 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 56320 sample count = 22 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index 56387fb3c7..98fe8c793d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 2560 sample count = 1 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index c98e27dc19..ecc28b7208 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index c73a6282e8..c5902f5d19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index 78b392053e..8fa0cbf7fe 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 110080 sample count = 43 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index 2558363342..603ca0de80 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 56320 sample count = 22 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index 084d2aa030..cd42dac917 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 2560 sample count = 1 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index c73a6282e8..c5902f5d19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -10,7 +10,6 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 From b4a88d63b70230a07d7386b91f21756ee0220fb7 Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 24 Nov 2022 12:13:31 +0000 Subject: [PATCH 018/104] Rollback of https://github.com/google/ExoPlayer/commit/ea3552c1a030d8b14c0cd0093f7eaa6242f969eb *** Original commit *** Rollback of https://github.com/google/ExoPlayer/commit/01eddb34f555903c576c79b02a7b34a53e0272cb *** Original commit *** Parse and set `peakBitrate` for Dolby TrueHD(AC-3) and (E-)AC-3 #minor-release *** *** PiperOrigin-RevId: 490707234 (cherry picked from commit 8c91a31ced401ce14911fbfdf58dda3c7ee8e643) --- .../com/google/android/exoplayer2/audio/Ac3Util.java | 10 +++++++++- .../assets/extractordumps/mp4/sample_ac3.mp4.0.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.1.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.2.dump | 2 ++ .../assets/extractordumps/mp4/sample_ac3.mp4.3.dump | 2 ++ .../mp4/sample_ac3.mp4.unknown_length.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.0.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.1.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.2.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.3.dump | 2 ++ .../mp4/sample_ac3_fragmented.mp4.unknown_length.dump | 2 ++ .../assets/extractordumps/mp4/sample_eac3.mp4.0.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.1.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.2.dump | 1 + .../assets/extractordumps/mp4/sample_eac3.mp4.3.dump | 1 + .../mp4/sample_eac3.mp4.unknown_length.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.0.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.1.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.2.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.3.dump | 1 + .../mp4/sample_eac3_fragmented.mp4.unknown_length.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.0.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.1.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.2.dump | 1 + .../extractordumps/mp4/sample_eac3joc.mp4.3.dump | 1 + .../mp4/sample_eac3joc.mp4.unknown_length.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.0.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.1.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.2.dump | 1 + .../mp4/sample_eac3joc_fragmented.mp4.3.dump | 1 + .../sample_eac3joc_fragmented.mp4.unknown_length.dump | 1 + 31 files changed, 49 insertions(+), 1 deletion(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index be79879dba..6a5bfd8559 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -156,6 +156,9 @@ public final class Ac3Util { if ((nextByte & 0x04) != 0) { // lfeon channelCount++; } + // bit_rate_code - 5 bits. 2 bits from previous byte and 3 bits from next. + int halfFrmsizecod = ((nextByte & 0x03) << 3) | ((data.readUnsignedByte() & 0xE0) >> 5); + int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; return new Format.Builder() .setId(trackId) .setSampleMimeType(MimeTypes.AUDIO_AC3) @@ -163,6 +166,8 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) + .setAverageBitrate(constantBitrate) + .setPeakBitrate(constantBitrate) .build(); } @@ -178,7 +183,9 @@ public final class Ac3Util { */ public static Format parseEAc3AnnexFFormat( ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { - data.skipBytes(2); // data_rate, num_ind_sub + // 13 bits for data_rate, 3 bits for num_ind_sub which are ignored. + int peakBitrate = + ((data.readUnsignedByte() & 0xFF) << 5) | ((data.readUnsignedByte() & 0xF8) >> 3); // Read the first independent substream. int fscod = (data.readUnsignedByte() & 0xC0) >> 6; @@ -214,6 +221,7 @@ public final class Ac3Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) + .setPeakBitrate(peakBitrate) .build(); } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump index c2e51faaef..71eed666b7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump index 80f0790cd0..a6fbd97784 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 9216 sample count = 6 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump index a8d1588940..e02699e2de 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 4608 sample count = 3 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump index 17bf79c850..4b7e17e7c9 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump index c2e51faaef..71eed666b7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump index 3724592554..84217c2e01 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump index e9019d4ab1..1edd06253f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 10752 sample count = 7 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump index 2b9cb1cd52..01fd6af916 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 6144 sample count = 4 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump index eb313f941d..c303da0e15 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump index 3724592554..84217c2e01 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump @@ -10,6 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: + averageBitrate = 384 + peakBitrate = 384 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index 8000864576..aba5268ea2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index 49ab3da0aa..ac03cfd484 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 144000 sample count = 36 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 19bfc7c5fa..1a61f528ac 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 72000 sample count = 18 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index d34514d8a8..431599a9be 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index 8000864576..aba5268ea2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index a7f3c63f8d..6da60d472a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index a627d00633..646dd35d91 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 148000 sample count = 37 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index 31013410b6..a7ba576bf5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 76000 sample count = 19 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index 13ff558eaa..280d6febc4 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index a7f3c63f8d..6da60d472a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 1000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index ecc28b7208..c98e27dc19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index d9ed0c417d..9c9cee29df 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 741d5199ea..85c07f6d2d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index 98fe8c793d..56387fb3c7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index ecc28b7208..c98e27dc19 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index c5902f5d19..c73a6282e8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index 8fa0cbf7fe..78b392053e 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index 603ca0de80..2558363342 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index cd42dac917..084d2aa030 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index c5902f5d19..c73a6282e8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -10,6 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 From 64b32c4bb56ac9da9e00d2b8b1d74b8788108fb7 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 24 Nov 2022 14:33:31 +0000 Subject: [PATCH 019/104] Remove two media3-only release notes from the ExoPlayer release notes Issue: google/ExoPlayer#10811 PiperOrigin-RevId: 490726544 (cherry picked from commit bb270c62cf2f7a1570fe22f87bb348a2d5e94dcf) --- RELEASENOTES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 226b18ba20..3bc2239e6c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -27,7 +27,6 @@ This release corresponds to the * Build: * Enforce minimum `compileSdkVersion` to avoid compilation errors ([#10684](https://github.com/google/ExoPlayer/issues/10684)). - * Avoid publishing block when included in another gradle build. * Track selection: * Prefer other tracks to Dolby Vision if display does not support it. ([#8944](https://github.com/google/ExoPlayer/issues/8944)). From 062f0e3dcb2a4aacf777fdd5b278238ef11edb73 Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 24 Nov 2022 17:15:07 +0000 Subject: [PATCH 020/104] Use `ParsableBitArray` instead of `ParsableByteArray` To avoid complicated bit shifting and masking. Also makes the code more readable. PiperOrigin-RevId: 490749482 (cherry picked from commit 89e4b8d049507efeb610f437429f25cf18df8f8b) --- .../android/exoplayer2/audio/Ac3Util.java | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 6a5bfd8559..2a2f86424f 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -149,16 +149,21 @@ public final class Ac3Util { */ public static Format parseAc3AnnexFFormat( ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { - int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + ParsableBitArray dataBitArray = new ParsableBitArray(); + dataBitArray.reset(data); + + int fscod = dataBitArray.readBits(2); int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - int nextByte = data.readUnsignedByte(); - int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3]; - if ((nextByte & 0x04) != 0) { // lfeon + dataBitArray.skipBits(8); // bsid, bsmod + int channelCount = CHANNEL_COUNT_BY_ACMOD[dataBitArray.readBits(3)]; // acmod + if (dataBitArray.readBits(1) != 0) { // lfeon channelCount++; } - // bit_rate_code - 5 bits. 2 bits from previous byte and 3 bits from next. - int halfFrmsizecod = ((nextByte & 0x03) << 3) | ((data.readUnsignedByte() & 0xE0) >> 5); + int halfFrmsizecod = dataBitArray.readBits(5); // bit_rate_code int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; + // Update data position + dataBitArray.byteAlign(); + data.setPosition(dataBitArray.getBytePosition()); return new Format.Builder() .setId(trackId) .setSampleMimeType(MimeTypes.AUDIO_AC3) @@ -183,37 +188,45 @@ public final class Ac3Util { */ public static Format parseEAc3AnnexFFormat( ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { - // 13 bits for data_rate, 3 bits for num_ind_sub which are ignored. - int peakBitrate = - ((data.readUnsignedByte() & 0xFF) << 5) | ((data.readUnsignedByte() & 0xF8) >> 3); + ParsableBitArray dataBitArray = new ParsableBitArray(); + dataBitArray.reset(data); + + int peakBitrate = dataBitArray.readBits(13); // data_rate + dataBitArray.skipBits(3); // num_ind_sub // Read the first independent substream. - int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + int fscod = dataBitArray.readBits(2); int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - int nextByte = data.readUnsignedByte(); - int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; - if ((nextByte & 0x01) != 0) { // lfeon + dataBitArray.skipBits(10); // bsid, reserved, asvc, bsmod + int channelCount = CHANNEL_COUNT_BY_ACMOD[dataBitArray.readBits(3)]; // acmod + if (dataBitArray.readBits(1) != 0) { // lfeon channelCount++; } // Read the first dependent substream. - nextByte = data.readUnsignedByte(); - int numDepSub = ((nextByte & 0x1E) >> 1); + dataBitArray.skipBits(3); // reserved + int numDepSub = dataBitArray.readBits(4); // num_dep_sub + dataBitArray.skipBits(1); // numDepSub > 0 ? LFE2 : reserved if (numDepSub > 0) { - int lowByteChanLoc = data.readUnsignedByte(); + dataBitArray.skipBytes(6); // other channel configurations // Read Lrs/Rrs pair // TODO: Read other channel configuration - if ((lowByteChanLoc & 0x02) != 0) { + if (dataBitArray.readBits(1) != 0) { channelCount += 2; } + dataBitArray.skipBits(1); // Lc/Rc pair } + String mimeType = MimeTypes.AUDIO_E_AC3; - if (data.bytesLeft() > 0) { - nextByte = data.readUnsignedByte(); - if ((nextByte & 0x01) != 0) { // flag_ec3_extension_type_a + if (dataBitArray.bitsLeft() > 7) { + dataBitArray.skipBits(7); // reserved + if (dataBitArray.readBits(1) != 0) { // flag_ec3_extension_type_a mimeType = MimeTypes.AUDIO_E_AC3_JOC; } } + // Update data position + dataBitArray.byteAlign(); + data.setPosition(dataBitArray.getBytePosition()); return new Format.Builder() .setId(trackId) .setSampleMimeType(mimeType) From 7e733aa2df79bf2fb36ab8ef6463b57fa50f4f1d Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 24 Nov 2022 18:14:44 +0000 Subject: [PATCH 021/104] Convert bitrates to bps before setting it Format expects the values of `averageBitrate` and `peakBitrate` in bps and the value fetched from AC3SpecificBox and EC3SpecificBox is in kbps. PiperOrigin-RevId: 490756581 (cherry picked from commit 67955e0ce331481bcb3fd94c9ffb9632f27eae6e) --- .../java/com/google/android/exoplayer2/audio/Ac3Util.java | 4 ++-- .../src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump | 4 ++-- .../src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump | 4 ++-- .../src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump | 4 ++-- .../src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump | 4 ++-- .../extractordumps/mp4/sample_ac3.mp4.unknown_length.dump | 4 ++-- .../extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump | 4 ++-- .../extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump | 4 ++-- .../extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump | 4 ++-- .../extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump | 4 ++-- .../mp4/sample_ac3_fragmented.mp4.unknown_length.dump | 4 ++-- .../src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump | 2 +- .../src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump | 2 +- .../src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump | 2 +- .../src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.unknown_length.dump | 2 +- .../extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.unknown_length.dump | 2 +- .../test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump | 2 +- .../test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump | 2 +- .../test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump | 2 +- .../test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump | 2 +- .../extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump | 2 +- .../extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump | 2 +- 31 files changed, 42 insertions(+), 42 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 2a2f86424f..07f2f710c6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -160,7 +160,7 @@ public final class Ac3Util { channelCount++; } int halfFrmsizecod = dataBitArray.readBits(5); // bit_rate_code - int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; + int constantBitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod] * 1000; // Update data position dataBitArray.byteAlign(); data.setPosition(dataBitArray.getBytePosition()); @@ -191,7 +191,7 @@ public final class Ac3Util { ParsableBitArray dataBitArray = new ParsableBitArray(); dataBitArray.reset(data); - int peakBitrate = dataBitArray.readBits(13); // data_rate + int peakBitrate = dataBitArray.readBits(13) * 1000; // data_rate dataBitArray.skipBits(3); // num_ind_sub // Read the first independent substream. diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump index 71eed666b7..97bfb758dc 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.0.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump index a6fbd97784..9e9b211ed9 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.1.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 9216 sample count = 6 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump index e02699e2de..4e872b3fd5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.2.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 4608 sample count = 3 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump index 4b7e17e7c9..138696f8a7 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.3.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump index 71eed666b7..97bfb758dc 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3.mp4.unknown_length.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 maxInputSize = 1566 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump index 84217c2e01..448e79e1fd 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.0.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump index 1edd06253f..0f902e441a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 10752 sample count = 7 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump index 01fd6af916..d747be40c5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 6144 sample count = 4 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump index c303da0e15..76738dd5b3 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.3.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 1536 sample count = 1 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump index 84217c2e01..448e79e1fd 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.unknown_length.dump @@ -10,8 +10,8 @@ track 0: total output bytes = 13824 sample count = 9 format 0: - averageBitrate = 384 - peakBitrate = 384 + averageBitrate = 384000 + peakBitrate = 384000 id = 1 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index aba5268ea2..a50ee9fecd 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index ac03cfd484..089c940439 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 144000 sample count = 36 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 1a61f528ac..5d481314d5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 72000 sample count = 18 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index 431599a9be..0242518866 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index aba5268ea2..a50ee9fecd 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 maxInputSize = 4030 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index 6da60d472a..734051bb2d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index 646dd35d91..027e7eb633 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 148000 sample count = 37 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index a7ba576bf5..db94e2636e 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 76000 sample count = 19 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index 280d6febc4..854d952ad8 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 4000 sample count = 1 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index 6da60d472a..734051bb2d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: - peakBitrate = 1000 + peakBitrate = 1000000 id = 1 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index c98e27dc19..45a51b50ae 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index 9c9cee29df..4ad3e45f53 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 85c07f6d2d..0c53717c22 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index 56387fb3c7..c8cd33b57b 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index c98e27dc19..45a51b50ae 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc maxInputSize = 2590 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index c73a6282e8..87930b0bfb 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index 78b392053e..e1aa764cfb 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 110080 sample count = 43 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index 2558363342..c9f083805f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 56320 sample count = 22 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index 084d2aa030..a3875f612d 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 2560 sample count = 1 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index c73a6282e8..87930b0bfb 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -10,7 +10,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: - peakBitrate = 640 + peakBitrate = 640000 id = 1 sampleMimeType = audio/eac3-joc channelCount = 6 From 0fb501ed6d34e2f15eb893c1138e6148607edacb Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 25 Nov 2022 15:36:22 +0000 Subject: [PATCH 022/104] Remove flakiness from DefaultAnalyticsCollectorTest Our FakeClock generally makes sure that playback tests are fully deterministic. However, this fails if the test uses blocking waits with clock.onThreadBlocked and where relevant Handlers are created without using the clock. To fix the flakiness, we can make the following adjustments: - Use TestExoPlayerBuilder instead of legacy ExoPlayerTestRunner to avoid onThreadBlocked calls. This also makes the tests more readable. - Use clock to create Handler for FakeVideoRenderer and FakeAudioRenderer. Ideally, this should be passed through RenderersFactory, but it's too disruptive given this is a public API. - Use clock for MediaSourceList and MediaPeriodQueue update handler. PiperOrigin-RevId: 490907495 (cherry picked from commit 7d62943bcd149eecce77cb44e4f867128ca374d3) --- .../exoplayer2/ExoPlayerImplInternal.java | 2 +- .../android/exoplayer2/MediaPeriodQueue.java | 5 +- .../android/exoplayer2/MediaSourceList.java | 167 +++-- .../android/exoplayer2/ExoPlayerTest.java | 21 +- .../exoplayer2/MediaPeriodQueueTest.java | 9 +- .../exoplayer2/MediaSourceListTest.java | 2 +- .../DefaultAnalyticsCollectorTest.java | 667 +++++++++--------- .../robolectric/TestPlayerRunHelper.java | 24 + .../testutil/FakeAudioRenderer.java | 29 +- .../testutil/FakeVideoRenderer.java | 51 +- .../testutil/TestExoPlayerBuilder.java | 18 +- 11 files changed, 570 insertions(+), 425 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 7680e6a0a5..440ed8f660 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 @@ -273,7 +273,7 @@ import java.util.concurrent.atomic.AtomicBoolean; deliverPendingMessageAtStartPositionRequired = true; - Handler eventHandler = new Handler(applicationLooper); + HandlerWrapper eventHandler = clock.createHandler(applicationLooper, /* callback= */ null); queue = new MediaPeriodQueue(analyticsCollector, eventHandler); mediaSourceList = new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId); 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 13cd603169..0651dd8bf8 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 @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.common.collect.ImmutableList; /** @@ -69,7 +70,7 @@ import com.google.common.collect.ImmutableList; private final Timeline.Period period; private final Timeline.Window window; private final AnalyticsCollector analyticsCollector; - private final Handler analyticsCollectorHandler; + private final HandlerWrapper analyticsCollectorHandler; private long nextWindowSequenceNumber; private @RepeatMode int repeatMode; @@ -89,7 +90,7 @@ import com.google.common.collect.ImmutableList; * on. */ public MediaPeriodQueue( - AnalyticsCollector analyticsCollector, Handler analyticsCollectorHandler) { + AnalyticsCollector analyticsCollector, HandlerWrapper analyticsCollectorHandler) { this.analyticsCollector = analyticsCollector; this.analyticsCollectorHandler = analyticsCollectorHandler; period = new Timeline.Period(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java index 389ae64b74..7c65a1540d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java @@ -15,10 +15,12 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.Math.max; import static java.lang.Math.min; import android.os.Handler; +import android.util.Pair; import androidx.annotation.Nullable; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.PlayerId; @@ -36,6 +38,7 @@ import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import java.io.IOException; @@ -47,6 +50,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.compatqual.NullableType; /** * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified @@ -76,11 +80,10 @@ import java.util.Set; private final IdentityHashMap mediaSourceByMediaPeriod; private final Map mediaSourceByUid; private final MediaSourceListInfoRefreshListener mediaSourceListInfoListener; - private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; - private final DrmSessionEventListener.EventDispatcher drmEventDispatcher; private final HashMap childSources; private final Set enabledMediaSourceHolders; - + private final AnalyticsCollector eventListener; + private final HandlerWrapper eventHandler; private ShuffleOrder shuffleOrder; private boolean isPrepared; @@ -100,7 +103,7 @@ import java.util.Set; public MediaSourceList( MediaSourceListInfoRefreshListener listener, AnalyticsCollector analyticsCollector, - Handler analyticsCollectorHandler, + HandlerWrapper analyticsCollectorHandler, PlayerId playerId) { this.playerId = playerId; mediaSourceListInfoListener = listener; @@ -108,12 +111,10 @@ import java.util.Set; mediaSourceByMediaPeriod = new IdentityHashMap<>(); mediaSourceByUid = new HashMap<>(); mediaSourceHolders = new ArrayList<>(); - mediaSourceEventDispatcher = new MediaSourceEventListener.EventDispatcher(); - drmEventDispatcher = new DrmSessionEventListener.EventDispatcher(); + eventListener = analyticsCollector; + eventHandler = analyticsCollectorHandler; childSources = new HashMap<>(); enabledMediaSourceHolders = new HashSet<>(); - mediaSourceEventDispatcher.addEventListener(analyticsCollectorHandler, analyticsCollector); - drmEventDispatcher.addEventListener(analyticsCollectorHandler, analyticsCollector); } /** @@ -307,7 +308,7 @@ import java.util.Set; Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid); MediaSource.MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(getChildPeriodUid(id.periodUid)); - MediaSourceHolder holder = Assertions.checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid)); + MediaSourceHolder holder = checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid)); enableMediaSource(holder); holder.activeMediaPeriodIds.add(childMediaPeriodId); MediaPeriod mediaPeriod = @@ -323,8 +324,7 @@ import java.util.Set; * @param mediaPeriod The period to release. */ public void releasePeriod(MediaPeriod mediaPeriod) { - MediaSourceHolder holder = - Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); + MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); holder.mediaSource.releasePeriod(mediaPeriod); holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id); if (!mediaSourceByMediaPeriod.isEmpty()) { @@ -449,8 +449,7 @@ import java.util.Set; private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) { // Release if the source has been removed from the playlist and no periods are still active. if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) { - MediaSourceAndListener removedChild = - Assertions.checkNotNull(childSources.remove(mediaSourceHolder)); + MediaSourceAndListener removedChild = checkNotNull(childSources.remove(mediaSourceHolder)); removedChild.mediaSource.releaseSource(removedChild.caller); removedChild.mediaSource.removeEventListener(removedChild.eventListener); removedChild.mediaSource.removeDrmEventListener(removedChild.eventListener); @@ -525,12 +524,8 @@ import java.util.Set; implements MediaSourceEventListener, DrmSessionEventListener { private final MediaSourceList.MediaSourceHolder id; - private MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; - private DrmSessionEventListener.EventDispatcher drmEventDispatcher; public ForwardingEventListener(MediaSourceList.MediaSourceHolder id) { - mediaSourceEventDispatcher = MediaSourceList.this.mediaSourceEventDispatcher; - drmEventDispatcher = MediaSourceList.this.drmEventDispatcher; this.id = id; } @@ -542,8 +537,14 @@ import java.util.Set; @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventData, MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.loadStarted(loadEventData, mediaLoadData); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onLoadStarted( + eventParameters.first, eventParameters.second, loadEventData, mediaLoadData)); } } @@ -553,8 +554,14 @@ import java.util.Set; @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventData, MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.loadCompleted(loadEventData, mediaLoadData); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onLoadCompleted( + eventParameters.first, eventParameters.second, loadEventData, mediaLoadData)); } } @@ -564,8 +571,14 @@ import java.util.Set; @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventData, MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.loadCanceled(loadEventData, mediaLoadData); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onLoadCanceled( + eventParameters.first, eventParameters.second, loadEventData, mediaLoadData)); } } @@ -577,8 +590,19 @@ import java.util.Set; MediaLoadData mediaLoadData, IOException error, boolean wasCanceled) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.loadError(loadEventData, mediaLoadData, error, wasCanceled); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onLoadError( + eventParameters.first, + eventParameters.second, + loadEventData, + mediaLoadData, + error, + wasCanceled)); } } @@ -587,8 +611,14 @@ import java.util.Set; int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.upstreamDiscarded(mediaLoadData); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onUpstreamDiscarded( + eventParameters.first, checkNotNull(eventParameters.second), mediaLoadData)); } } @@ -597,8 +627,14 @@ import java.util.Set; int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - mediaSourceEventDispatcher.downstreamFormatChanged(mediaLoadData); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onDownstreamFormatChanged( + eventParameters.first, eventParameters.second, mediaLoadData)); } } @@ -609,75 +645,94 @@ import java.util.Set; int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, @DrmSession.State int state) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmSessionAcquired(state); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onDrmSessionAcquired( + eventParameters.first, eventParameters.second, state)); } } @Override public void onDrmKeysLoaded( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmKeysLoaded(); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> eventListener.onDrmKeysLoaded(eventParameters.first, eventParameters.second)); } } @Override public void onDrmSessionManagerError( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, Exception error) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmSessionManagerError(error); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onDrmSessionManagerError( + eventParameters.first, eventParameters.second, error)); } } @Override public void onDrmKeysRestored( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmKeysRestored(); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> eventListener.onDrmKeysRestored(eventParameters.first, eventParameters.second)); } } @Override public void onDrmKeysRemoved( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmKeysRemoved(); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> eventListener.onDrmKeysRemoved(eventParameters.first, eventParameters.second)); } } @Override public void onDrmSessionReleased( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - drmEventDispatcher.drmSessionReleased(); + @Nullable + Pair eventParameters = + getEventParameters(windowIndex, mediaPeriodId); + if (eventParameters != null) { + eventHandler.post( + () -> + eventListener.onDrmSessionReleased(eventParameters.first, eventParameters.second)); } } - /** Updates the event dispatcher and returns whether the event should be dispatched. */ - private boolean maybeUpdateEventDispatcher( + /** Updates the event parameters and returns whether the event should be dispatched. */ + @Nullable + private Pair getEventParameters( int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) { @Nullable MediaSource.MediaPeriodId mediaPeriodId = null; if (childMediaPeriodId != null) { mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId); if (mediaPeriodId == null) { // Media period not found. Ignore event. - return false; + return null; } } int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex); - if (mediaSourceEventDispatcher.windowIndex != windowIndex - || !Util.areEqual(mediaSourceEventDispatcher.mediaPeriodId, mediaPeriodId)) { - mediaSourceEventDispatcher = - MediaSourceList.this.mediaSourceEventDispatcher.withParameters( - windowIndex, mediaPeriodId, /* mediaTimeOffsetMs= */ 0L); - } - if (drmEventDispatcher.windowIndex != windowIndex - || !Util.areEqual(drmEventDispatcher.mediaPeriodId, mediaPeriodId)) { - drmEventDispatcher = - MediaSourceList.this.drmEventDispatcher.withParameters(windowIndex, mediaPeriodId); - } - return true; + return Pair.create(windowIndex, mediaPeriodId); } } } 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 1c1662ea97..0af7aa0def 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 @@ -151,6 +151,7 @@ import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.SystemClock; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -11887,7 +11888,11 @@ public final class ExoPlayerTest { new TestExoPlayerBuilder(context) .setRenderersFactory( (handler, videoListener, audioListener, textOutput, metadataOutput) -> { - videoRenderer.set(new FakeVideoRenderer(handler, videoListener)); + videoRenderer.set( + new FakeVideoRenderer( + SystemClock.DEFAULT.createHandler( + handler.getLooper(), /* callback= */ null), + videoListener)); return new Renderer[] {videoRenderer.get()}; }) .build(); @@ -12024,7 +12029,12 @@ public final class ExoPlayerTest { new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()) .setRenderersFactory( (handler, videoListener, audioListener, textOutput, metadataOutput) -> - new Renderer[] {new FakeVideoRenderer(handler, videoListener)}) + new Renderer[] { + new FakeVideoRenderer( + SystemClock.DEFAULT.createHandler( + handler.getLooper(), /* callback= */ null), + videoListener) + }) .build(); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); @@ -12049,7 +12059,12 @@ public final class ExoPlayerTest { new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()) .setRenderersFactory( (handler, videoListener, audioListener, textOutput, metadataOutput) -> - new Renderer[] {new FakeVideoRenderer(handler, videoListener)}) + new Renderer[] { + new FakeVideoRenderer( + SystemClock.DEFAULT.createHandler( + handler.getLooper(), /* callback= */ null), + videoListener) + }) .build(); Player.Listener listener = mock(Player.Listener.class); player.addListener(listener); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index db0f15cf11..6e5a4bed85 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.mock; import static org.robolectric.Shadows.shadowOf; import android.net.Uri; -import android.os.Handler; import android.os.Looper; import android.util.Pair; import androidx.test.core.app.ApplicationProvider; @@ -48,6 +47,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.common.collect.ImmutableList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -91,13 +91,14 @@ public final class MediaPeriodQueueTest { analyticsCollector.setPlayer( new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(), Looper.getMainLooper()); - mediaPeriodQueue = - new MediaPeriodQueue(analyticsCollector, new Handler(Looper.getMainLooper())); + HandlerWrapper handler = + Clock.DEFAULT.createHandler(Looper.getMainLooper(), /* callback= */ null); + mediaPeriodQueue = new MediaPeriodQueue(analyticsCollector, handler); mediaSourceList = new MediaSourceList( mock(MediaSourceList.MediaSourceListInfoRefreshListener.class), analyticsCollector, - new Handler(Looper.getMainLooper()), + handler, PlayerId.UNSET); rendererCapabilities = new RendererCapabilities[0]; trackSelector = mock(TrackSelector.class); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java index 71618bb690..fbe57cf0da 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java @@ -64,7 +64,7 @@ public class MediaSourceListTest { new MediaSourceList( mock(MediaSourceList.MediaSourceListInfoRefreshListener.class), analyticsCollector, - Util.createHandlerForCurrentOrMainLooper(), + Clock.DEFAULT.createHandler(Util.getCurrentOrMainLooper(), /* callback= */ null), PlayerId.UNSET); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollectorTest.java index f62d75955d..4985169b6f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollectorTest.java @@ -47,6 +47,12 @@ import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VI import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilError; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilIsLoading; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilTimelineChanged; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.common.truth.Truth.assertThat; @@ -63,6 +69,8 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.robolectric.shadows.ShadowLooper.idleMainLooper; +import static org.robolectric.shadows.ShadowLooper.runMainLooperToNextTask; import android.graphics.SurfaceTexture; import android.os.Looper; @@ -101,8 +109,6 @@ import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.ads.AdPlaybackState; -import com.google.android.exoplayer2.testutil.ActionSchedule; -import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.FakeAudioRenderer; import com.google.android.exoplayer2.testutil.FakeClock; @@ -116,6 +122,7 @@ import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; @@ -132,14 +139,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import org.robolectric.shadows.ShadowLooper; /** Integration test for {@link DefaultAnalyticsCollector}. */ @RunWith(AndroidJUnit4.class) public final class DefaultAnalyticsCollectorTest { - private static final String TAG = "DefaultAnalyticsCollectorTest"; - // Deprecated event constants. private static final long EVENT_PLAYER_STATE_CHANGED = 1L << 63; private static final long EVENT_SEEK_STARTED = 1L << 62; @@ -167,7 +171,6 @@ public final class DefaultAnalyticsCollectorTest { private static final Format VIDEO_FORMAT_DRM_1 = ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setDrmInitData(DRM_DATA_1).build(); - private static final int TIMEOUT_MS = 10_000; private static final Timeline SINGLE_PERIOD_TIMELINE = new FakeTimeline(); private static final EventWindowAndPeriodId WINDOW_0 = new EventWindowAndPeriodId(/* windowIndex= */ 0, /* mediaPeriodId= */ null); @@ -217,7 +220,14 @@ public final class DefaultAnalyticsCollectorTest { FakeMediaSource mediaSource = new FakeMediaSource( Timeline.EMPTY, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( @@ -236,7 +246,14 @@ public final class DefaultAnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) @@ -247,7 +264,7 @@ public final class DefaultAnalyticsCollectorTest { period0 /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */) + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */) .inOrder(); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) .containsExactly(period0 /* started */, period0 /* stopped */) @@ -297,7 +314,14 @@ public final class DefaultAnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT)); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) @@ -378,7 +402,14 @@ public final class DefaultAnalyticsCollectorTest { new ConcatenatingMediaSource( new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT), new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT)); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) @@ -449,23 +480,23 @@ public final class DefaultAnalyticsCollectorTest { ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT), new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT)); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - // Wait until second period has fully loaded to assert loading events without flakiness. - .waitForIsLoading(true) - .waitForIsLoading(false) - .seek(/* mediaItemIndex= */ 1, /* positionMs= */ 0) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(mediaSource); + player.prepare(); + // Wait until second period has fully loaded to assert loading events. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, - WINDOW_0 /* setPlayWhenReady=false */, period0 /* READY */, period1 /* BUFFERING */, period1 /* setPlayWhenReady=true */, @@ -542,23 +573,24 @@ public final class DefaultAnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT)); - long periodDurationMs = + long windowDurationMs = SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .playUntilPosition(/* mediaItemIndex= */ 0, periodDurationMs) - .seekAndWait(/* positionMs= */ 0) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + playUntilPosition(player, /* mediaItemIndex= */ 0, windowDurationMs - 100); + player.seekTo(/* positionMs= */ 0); + runUntilPlaybackState(player, Player.STATE_READY); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, period0 /* READY */, period0 /* setPlayWhenReady=true */, @@ -653,17 +685,19 @@ public final class DefaultAnalyticsCollectorTest { new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT); MediaSource mediaSource2 = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .setMediaSources(/* resetPosition= */ false, mediaSource2) - .waitForTimelineChanged() - // Wait until loading started to prevent flakiness caused by loading finishing too fast. - .waitForIsLoading(true) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource1, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(mediaSource1); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + player.setMediaSource(mediaSource2, /* resetPosition= */ false); + runUntilTimelineChanged(player); + // Wait until loading started to assert loading events. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); // Populate all event ids with last timeline (after second prepare). populateEventIds(listener.lastReportedTimeline); @@ -676,9 +710,7 @@ public final class DefaultAnalyticsCollectorTest { /* windowSequenceNumber= */ 0)); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, - WINDOW_0 /* setPlayWhenReady=false */, period0Seq0 /* READY */, WINDOW_0 /* BUFFERING */, period0Seq1 /* setPlayWhenReady=true */, @@ -688,9 +720,9 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGE */, - period0Seq0 /* SOURCE_UPDATE */, + WINDOW_0 /* SOURCE_UPDATE */, WINDOW_0 /* PLAYLIST_CHANGE */, - period0Seq1 /* SOURCE_UPDATE */); + WINDOW_0 /* SOURCE_UPDATE */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) .containsExactly(WINDOW_0 /* REMOVE */); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) @@ -753,28 +785,31 @@ public final class DefaultAnalyticsCollectorTest { public void reprepareAfterError() throws Exception { MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .throwPlaybackException( - ExoPlaybackException.createForSource( - new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED)) - .waitForPlaybackState(Player.STATE_IDLE) - .seek(/* positionMs= */ 0) - .prepare() - // Wait until loading started to assert loading events without flakiness. - .waitForIsLoading(true) - .play() - .waitForPlaybackState(Player.STATE_ENDED) - .build(); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + player + .createMessage( + (message, payload) -> { + throw ExoPlaybackException.createForSource( + new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + }) + .send(); + runUntilError(player); + player.seekTo(/* positionMs= */ 0); + player.prepare(); + // Wait until loading started to assert loading events. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, period0Seq0 /* READY */, period0Seq0 /* IDLE */, @@ -784,7 +819,7 @@ public final class DefaultAnalyticsCollectorTest { period0Seq0 /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* prepared */, period0Seq0 /* prepared */); + .containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* prepared */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0Seq0); @@ -835,36 +870,33 @@ public final class DefaultAnalyticsCollectorTest { new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT); final ConcatenatingMediaSource concatenatedMediaSource = new ConcatenatingMediaSource(childMediaSource, childMediaSource); - long periodDurationMs = + long windowDurationMs = SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - // Ensure second period is already being read from. - .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ periodDurationMs) - .executeRunnable( - () -> - concatenatedMediaSource.moveMediaSource( - /* currentIndex= */ 0, /* newIndex= */ 1)) - .waitForTimelineChanged() - .waitForPlaybackState(Player.STATE_READY) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(concatenatedMediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(concatenatedMediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + // Ensure second period is already being read from. + playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ windowDurationMs - 100); + concatenatedMediaSource.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 1); + runUntilTimelineChanged(player); + runUntilPlaybackState(player, Player.STATE_READY); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, window0Period1Seq0 /* READY */, window0Period1Seq0 /* setPlayWhenReady=true */, window0Period1Seq0 /* setPlayWhenReady=false */, - period1Seq0 /* setPlayWhenReady=true */, period1Seq0 /* BUFFERING */, period1Seq0 /* READY */, + period1Seq0 /* setPlayWhenReady=true */, period1Seq0 /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) @@ -926,20 +958,22 @@ public final class DefaultAnalyticsCollectorTest { public void playlistOperations() throws Exception { MediaSource fakeMediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .addMediaSources(fakeMediaSource) - // Wait until second period has fully loaded to assert loading events without flakiness. - .waitForIsLoading(true) - .waitForIsLoading(false) - .removeMediaItem(/* index= */ 0) - .waitForPlaybackState(Player.STATE_BUFFERING) - .waitForPlaybackState(Player.STATE_READY) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(fakeMediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + player.addMediaSource(fakeMediaSource); + // Wait until second period has fully loaded to assert loading events. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + player.removeMediaItem(/* index= */ 0); + runUntilPlaybackState(player, Player.STATE_BUFFERING); + runUntilPlaybackState(player, Player.STATE_READY); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); // Populate event ids with second to last timeline that still contained both periods. populateEventIds(listener.reportedTimelines.get(listener.reportedTimelines.size() - 2)); @@ -953,8 +987,6 @@ public final class DefaultAnalyticsCollectorTest { /* windowSequenceNumber= */ 1)); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, period0Seq0 /* READY */, period0Seq1 /* BUFFERING */, @@ -965,7 +997,7 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGED */, - period0Seq0 /* SOURCE_UPDATE (first item) */, + WINDOW_0 /* SOURCE_UPDATE (first item) */, period0Seq0 /* PLAYLIST_CHANGED (add) */, period0Seq0 /* SOURCE_UPDATE (second item) */, period0Seq1 /* PLAYLIST_CHANGED (remove) */) @@ -1063,60 +1095,53 @@ public final class DefaultAnalyticsCollectorTest { } }, ExoPlayerTestRunner.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(ExoPlayer player) { - player.addListener( - new Player.Listener() { - @Override - public void onPositionDiscontinuity( - Player.PositionInfo oldPosition, - Player.PositionInfo newPosition, - @Player.DiscontinuityReason int reason) { - if (!player.isPlayingAd() - && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { - // Finished playing ad. Marked as played. - adPlaybackState.set( - adPlaybackState - .get() - .withPlayedAd( - /* adGroupIndex= */ playedAdCount.getAndIncrement(), - /* adIndexInAdGroup= */ 0)); - fakeMediaSource.setNewSourceInfo( - new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ true, - /* isDynamic= */ false, - contentDurationsUs, - adPlaybackState.get())), - /* sendManifestLoadEvents= */ false); - } - } - }); - } - }) - .pause() - // Ensure everything is preloaded. - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForPlaybackState(Player.STATE_READY) - // Wait in each content part to ensure previously triggered events get a chance to be - // delivered. This prevents flakiness caused by playback progressing too fast. - .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 3_000) - .waitForPendingPlayerCommands() - .playUntilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 8_000) - .waitForPendingPlayerCommands() - .play() - .waitForPlaybackState(Player.STATE_ENDED) - // Wait for final timeline change that marks post-roll played. - .waitForTimelineChanged() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + player.addListener( + new Player.Listener() { + @Override + public void onPositionDiscontinuity( + Player.PositionInfo oldPosition, + Player.PositionInfo newPosition, + @Player.DiscontinuityReason int reason) { + if (!player.isPlayingAd() && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { + // Finished playing ad. Marked as played. + adPlaybackState.set( + adPlaybackState + .get() + .withPlayedAd( + /* adGroupIndex= */ playedAdCount.getAndIncrement(), + /* adIndexInAdGroup= */ 0)); + fakeMediaSource.setNewSourceInfo( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + contentDurationsUs, + adPlaybackState.get())), + /* sendManifestLoadEvents= */ false); + } + } + }); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(fakeMediaSource); + player.prepare(); + // Ensure everything is preloaded. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + runUntilPlaybackState(player, Player.STATE_READY); + // Wait in each content part to ensure previously triggered events get a chance to be delivered. + playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 3_000); + runUntilPendingCommandsAreFullyHandled(player); + playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 8_000); + runUntilPendingCommandsAreFullyHandled(player); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + // Wait for final timeline change that marks post-roll played. + runUntilTimelineChanged(player); Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); EventWindowAndPeriodId prerollAd = @@ -1158,8 +1183,6 @@ public final class DefaultAnalyticsCollectorTest { periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET)); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, prerollAd /* READY */, prerollAd /* setPlayWhenReady=true */, @@ -1172,7 +1195,7 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGED */, - prerollAd /* SOURCE_UPDATE (initial) */, + WINDOW_0 /* SOURCE_UPDATE (initial) */, contentAfterPreroll /* SOURCE_UPDATE (played preroll) */, contentAfterMidroll /* SOURCE_UPDATE (played midroll) */, contentAfterPostroll /* SOURCE_UPDATE (played postroll) */) @@ -1322,20 +1345,21 @@ public final class DefaultAnalyticsCollectorTest { } }, ExoPlayerTestRunner.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - // Ensure everything is preloaded. - .waitForIsLoading(true) - .waitForIsLoading(false) - // Seek behind the midroll. - .seek(6 * C.MICROS_PER_SECOND) - // Wait until loading started again to assert loading events without flakiness. - .waitForIsLoading(true) - .play() - .waitForPlaybackState(Player.STATE_ENDED) - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(fakeMediaSource); + player.prepare(); + // Ensure everything is preloaded. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + // Seek behind the midroll. + player.seekTo(/* positionMs= */ 6_000); + // Wait until loading started again to assert loading events. + runUntilIsLoading(player, /* expectedIsLoading= */ true); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); EventWindowAndPeriodId midrollAd = @@ -1357,8 +1381,6 @@ public final class DefaultAnalyticsCollectorTest { periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET)); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, contentBeforeMidroll /* READY */, contentAfterMidroll /* BUFFERING */, @@ -1367,7 +1389,7 @@ public final class DefaultAnalyticsCollectorTest { contentAfterMidroll /* ENDED */) .inOrder(); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, contentBeforeMidroll /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) .containsExactly( contentAfterMidroll /* seek */, @@ -1435,21 +1457,17 @@ public final class DefaultAnalyticsCollectorTest { @Test public void notifyExternalEvents() throws Exception { MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE); - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(ExoPlayer player) { - player.getAnalyticsCollector().notifySeekStarted(); - } - }) - .seek(/* positionMs= */ 0) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + player.getAnalyticsCollector().notifySeekStarted(); + player.seekTo(/* positionMs= */ 0); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); @@ -1460,7 +1478,14 @@ public final class DefaultAnalyticsCollectorTest { public void drmEvents_singlePeriod() throws Exception { MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty(); @@ -1488,18 +1513,21 @@ public final class DefaultAnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1), new FakeMediaSource( SINGLE_PERIOD_TIMELINE, blockingDrmSessionManager, VIDEO_FORMAT_DRM_1)); - TestAnalyticsListener listener = - runAnalyticsTest( - mediaSource, - // Wait for the media to be fully buffered before unblocking the DRM key request. This - // ensures both periods report the same load event (because period1's DRM session is - // already preacquired by the time the key load completes). - new ActionSchedule.Builder(TAG) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .executeRunnable(mediaDrmCallback.keyCondition::open) - .build()); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + // Wait for the media to be fully buffered before unblocking the DRM key request. This + // ensures both periods report the same load event (because period1's DRM session is + // already preacquired by the time the key load completes). + runUntilIsLoading(player, /* expectedIsLoading= */ false); + runUntilIsLoading(player, /* expectedIsLoading= */ true); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + mediaDrmCallback.keyCondition.open(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty(); @@ -1525,7 +1553,14 @@ public final class DefaultAnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, drmSessionManager, VIDEO_FORMAT_DRM_1.buildUpon().setDrmInitData(DRM_DATA_2).build())); - TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty(); @@ -1552,13 +1587,16 @@ public final class DefaultAnalyticsCollectorTest { .build(mediaDrmCallback); MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1); - TestAnalyticsListener listener = - runAnalyticsTest( - mediaSource, - new ActionSchedule.Builder(TAG) - .waitForIsLoading(false) - .executeRunnable(mediaDrmCallback.keyCondition::open) - .build()); + ExoPlayer player = setupPlayer(); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); + + player.play(); + player.setMediaSource(mediaSource); + player.prepare(); + runUntilIsLoading(player, /* expectedIsLoading= */ false); + mediaDrmCallback.keyCondition.open(); + runUntilError(player); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0); @@ -1588,12 +1626,14 @@ public final class DefaultAnalyticsCollectorTest { } } }; + ExoPlayer player = setupPlayer(renderersFactory); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); - TestAnalyticsListener listener = - runAnalyticsTest( - new ConcatenatingMediaSource(source0, source1), - /* actionSchedule= */ null, - renderersFactory); + player.play(); + player.setMediaSource(new ConcatenatingMediaSource(source0, source1)); + player.prepare(); + runUntilError(player); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1); @@ -1622,12 +1662,14 @@ public final class DefaultAnalyticsCollectorTest { } } }; + ExoPlayer player = setupPlayer(renderersFactory); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); - TestAnalyticsListener listener = - runAnalyticsTest( - new ConcatenatingMediaSource(source0, source1), - /* actionSchedule= */ null, - renderersFactory); + player.play(); + player.setMediaSource(new ConcatenatingMediaSource(source0, source1)); + player.prepare(); + runUntilError(player); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1); @@ -1660,12 +1702,14 @@ public final class DefaultAnalyticsCollectorTest { } } }; + ExoPlayer player = setupPlayer(renderersFactory); + TestAnalyticsListener listener = new TestAnalyticsListener(); + player.addAnalyticsListener(listener); - TestAnalyticsListener listener = - runAnalyticsTest( - new ConcatenatingMediaSource(source, source), - /* actionSchedule= */ null, - renderersFactory); + player.play(); + player.setMediaSource(new ConcatenatingMediaSource(source, source)); + player.prepare(); + runUntilError(player); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1); @@ -1673,11 +1717,7 @@ public final class DefaultAnalyticsCollectorTest { @Test public void onEvents_isReportedWithCorrectEventTimes() throws Exception { - ExoPlayer player = - new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); - Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0)); - player.setVideoSurface(surface); - + ExoPlayer player = setupPlayer(); AnalyticsListener listener = mock(AnalyticsListener.class); Format[] formats = new Format[] { @@ -1690,20 +1730,18 @@ public final class DefaultAnalyticsCollectorTest { player.setMediaSource(new FakeMediaSource(new FakeTimeline(), formats)); player.seekTo(2_000); player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f)); - ShadowLooper.runMainLooperToNextTask(); - + runMainLooperToNextTask(); // Move to another item and fail with a third one to trigger events with different EventTimes. player.prepare(); - TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); + runUntilPlaybackState(player, Player.STATE_READY); player.addMediaSource(new FakeMediaSource(new FakeTimeline(), formats)); player.play(); TestPlayerRunHelper.runUntilPositionDiscontinuity( player, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4")); - TestPlayerRunHelper.runUntilError(player); - ShadowLooper.runMainLooperToNextTask(); + runUntilError(player); + runMainLooperToNextTask(); player.release(); - surface.release(); // Verify that expected individual callbacks have been called and capture EventTimes. ArgumentCaptor individualTimelineChangedEventTimes = @@ -1928,48 +1966,6 @@ public final class DefaultAnalyticsCollectorTest { .inOrder(); } - private void populateEventIds(Timeline timeline) { - period0 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); - period0Seq0 = period0; - period0Seq1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1)); - window1Period0Seq1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1)); - if (timeline.getPeriodCount() > 1) { - period1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); - period1Seq1 = period1; - period1Seq0 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); - period1Seq2 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 2)); - window0Period1Seq0 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); - } - } - @Test public void recursiveListenerInvocation_arrivesInCorrectOrder() { AnalyticsCollector analyticsCollector = new DefaultAnalyticsCollector(Clock.DEFAULT); @@ -2027,13 +2023,12 @@ public final class DefaultAnalyticsCollectorTest { exoPlayer.setMediaSource( new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT)); exoPlayer.prepare(); - TestPlayerRunHelper.runUntilPlaybackState(exoPlayer, Player.STATE_READY); - + runUntilPlaybackState(exoPlayer, Player.STATE_READY); // Release and add delay on releasing thread to verify timestamps of events. exoPlayer.release(); long releaseTimeMs = fakeClock.currentTimeMillis(); fakeClock.advanceTime(1); - ShadowLooper.idleMainLooper(); + idleMainLooper(); // Verify video disable events and release events arrived in order. ArgumentCaptor videoDisabledEventTime = @@ -2059,49 +2054,79 @@ public final class DefaultAnalyticsCollectorTest { assertThat(releasedEventTime.getValue().realtimeMs).isGreaterThan(videoDisableTimeMs); } - private static TestAnalyticsListener runAnalyticsTest(MediaSource mediaSource) throws Exception { - return runAnalyticsTest(mediaSource, /* actionSchedule= */ null); + private void populateEventIds(Timeline timeline) { + period0 = + new EventWindowAndPeriodId( + /* windowIndex= */ 0, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); + period0Seq0 = period0; + period0Seq1 = + new EventWindowAndPeriodId( + /* windowIndex= */ 0, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1)); + window1Period0Seq1 = + new EventWindowAndPeriodId( + /* windowIndex= */ 1, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1)); + if (timeline.getPeriodCount() > 1) { + period1 = + new EventWindowAndPeriodId( + /* windowIndex= */ 1, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); + period1Seq1 = period1; + period1Seq0 = + new EventWindowAndPeriodId( + /* windowIndex= */ 1, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); + period1Seq2 = + new EventWindowAndPeriodId( + /* windowIndex= */ 1, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 2)); + window0Period1Seq0 = + new EventWindowAndPeriodId( + /* windowIndex= */ 0, + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); + } } - private static TestAnalyticsListener runAnalyticsTest( - MediaSource mediaSource, @Nullable ActionSchedule actionSchedule) throws Exception { - RenderersFactory renderersFactory = - (eventHandler, + private static ExoPlayer setupPlayer() { + Clock clock = new FakeClock(/* isAutoAdvancing= */ true); + return setupPlayer( + /* renderersFactory= */ (eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, - metadataRendererOutput) -> - new Renderer[] { - new FakeVideoRenderer(eventHandler, videoRendererEventListener), - new FakeAudioRenderer(eventHandler, audioRendererEventListener) - }; - return runAnalyticsTest(mediaSource, actionSchedule, renderersFactory); + metadataRendererOutput) -> { + HandlerWrapper clockAwareHandler = + clock.createHandler(eventHandler.getLooper(), /* callback= */ null); + return new Renderer[] { + new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener), + new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener) + }; + }, + clock); } - private static TestAnalyticsListener runAnalyticsTest( - MediaSource mediaSource, - @Nullable ActionSchedule actionSchedule, - RenderersFactory renderersFactory) - throws Exception { + private static ExoPlayer setupPlayer(RenderersFactory renderersFactory) { + return setupPlayer(renderersFactory, new FakeClock(/* isAutoAdvancing= */ true)); + } + + private static ExoPlayer setupPlayer(RenderersFactory renderersFactory, Clock clock) { Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0)); - TestAnalyticsListener listener = new TestAnalyticsListener(); - try { - new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext()) - .setMediaSources(mediaSource) - .setRenderersFactory(renderersFactory) - .setVideoSurface(surface) - .setAnalyticsListener(listener) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilActionScheduleFinished(TIMEOUT_MS) - .blockUntilEnded(TIMEOUT_MS); - } catch (ExoPlaybackException e) { - // Ignore ExoPlaybackException as these may be expected. - } finally { - surface.release(); - } - return listener; + ExoPlayer player = + new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()) + .setClock(clock) + .setRenderersFactory(renderersFactory) + .build(); + player.setVideoSurface(surface); + return player; } private static final class EventWindowAndPeriodId { diff --git a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java index aa878c3d4b..66cb4c9ddf 100644 --- a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java +++ b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.java @@ -89,6 +89,30 @@ public class TestPlayerRunHelper { } } + /** + * Runs tasks of the main {@link Looper} until {@link Player#isLoading()} matches the expected + * value or a playback error occurs. + * + *

    If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}. + * + * @param player The {@link Player}. + * @param expectedIsLoading The expected value for {@link Player#isLoading()}. + * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is + * exceeded. + */ + public static void runUntilIsLoading(Player player, boolean expectedIsLoading) + throws TimeoutException { + verifyMainTestThread(player); + if (player instanceof ExoPlayer) { + verifyPlaybackThreadIsAlive((ExoPlayer) player); + } + runMainLooperUntil( + () -> player.isLoading() == expectedIsLoading || player.getPlayerError() != null); + if (player.getPlayerError() != null) { + throw new IllegalStateException(player.getPlayerError()); + } + } + /** * Runs tasks of the main {@link Looper} until {@link Player#getCurrentTimeline()} matches the * expected timeline or a playback error occurs. diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java index eb02f675c2..4d23be2261 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java @@ -16,24 +16,26 @@ package com.google.android.exoplayer2.testutil; -import android.os.Handler; import android.os.SystemClock; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.util.HandlerWrapper; /** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_AUDIO}. */ public class FakeAudioRenderer extends FakeRenderer { - private final AudioRendererEventListener.EventDispatcher eventDispatcher; + private final HandlerWrapper handler; + private final AudioRendererEventListener eventListener; private final DecoderCounters decoderCounters; private boolean notifiedPositionAdvancing; - public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) { + public FakeAudioRenderer(HandlerWrapper handler, AudioRendererEventListener eventListener) { super(C.TRACK_TYPE_AUDIO); - eventDispatcher = new AudioRendererEventListener.EventDispatcher(handler, eventListener); + this.handler = handler; + this.eventListener = eventListener; decoderCounters = new DecoderCounters(); } @@ -41,30 +43,33 @@ public class FakeAudioRenderer extends FakeRenderer { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); - eventDispatcher.enabled(decoderCounters); + handler.post(() -> eventListener.onAudioEnabled(decoderCounters)); notifiedPositionAdvancing = false; } @Override protected void onDisabled() { super.onDisabled(); - eventDispatcher.disabled(decoderCounters); + handler.post(() -> eventListener.onAudioDisabled(decoderCounters)); } @Override protected void onFormatChanged(Format format) { - eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null); - eventDispatcher.decoderInitialized( - /* decoderName= */ "fake.audio.decoder", - /* initializedTimestampMs= */ SystemClock.elapsedRealtime(), - /* initializationDurationMs= */ 0); + handler.post( + () -> eventListener.onAudioInputFormatChanged(format, /* decoderReuseEvaluation= */ null)); + handler.post( + () -> + eventListener.onAudioDecoderInitialized( + /* decoderName= */ "fake.audio.decoder", + /* initializedTimestampMs= */ SystemClock.elapsedRealtime(), + /* initializationDurationMs= */ 0)); } @Override protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) { boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs); if (shouldProcess && !notifiedPositionAdvancing) { - eventDispatcher.positionAdvancing(System.currentTimeMillis()); + handler.post(() -> eventListener.onAudioPositionAdvancing(System.currentTimeMillis())); notifiedPositionAdvancing = true; } return shouldProcess; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java index ad30d53bae..41303e12ce 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.testutil; -import android.os.Handler; import android.os.SystemClock; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -25,6 +24,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoSize; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -32,7 +32,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */ public class FakeVideoRenderer extends FakeRenderer { - private final VideoRendererEventListener.EventDispatcher eventDispatcher; + private final HandlerWrapper handler; + private final VideoRendererEventListener eventListener; private final DecoderCounters decoderCounters; private @MonotonicNonNull Format format; @Nullable private Object output; @@ -41,9 +42,10 @@ public class FakeVideoRenderer extends FakeRenderer { private boolean mayRenderFirstFrameAfterEnableIfNotStarted; private boolean renderedFirstFrameAfterEnable; - public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) { + public FakeVideoRenderer(HandlerWrapper handler, VideoRendererEventListener eventListener) { super(C.TRACK_TYPE_VIDEO); - eventDispatcher = new VideoRendererEventListener.EventDispatcher(handler, eventListener); + this.handler = handler; + this.eventListener = eventListener; decoderCounters = new DecoderCounters(); } @@ -51,7 +53,7 @@ public class FakeVideoRenderer extends FakeRenderer { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); - eventDispatcher.enabled(decoderCounters); + handler.post(() -> eventListener.onVideoEnabled(decoderCounters)); mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream; renderedFirstFrameAfterEnable = false; } @@ -67,15 +69,17 @@ public class FakeVideoRenderer extends FakeRenderer { @Override protected void onStopped() { super.onStopped(); - eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0); - eventDispatcher.reportVideoFrameProcessingOffset( - /* totalProcessingOffsetUs= */ 400000, /* frameCount= */ 10); + handler.post(() -> eventListener.onDroppedFrames(/* count= */ 0, /* elapsedMs= */ 0)); + handler.post( + () -> + eventListener.onVideoFrameProcessingOffset( + /* totalProcessingOffsetUs= */ 400000, /* frameCount= */ 10)); } @Override protected void onDisabled() { super.onDisabled(); - eventDispatcher.disabled(decoderCounters); + handler.post(() -> eventListener.onVideoDisabled(decoderCounters)); } @Override @@ -86,11 +90,14 @@ public class FakeVideoRenderer extends FakeRenderer { @Override protected void onFormatChanged(Format format) { - eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null); - eventDispatcher.decoderInitialized( - /* decoderName= */ "fake.video.decoder", - /* initializedTimestampMs= */ SystemClock.elapsedRealtime(), - /* initializationDurationMs= */ 0); + handler.post( + () -> eventListener.onVideoInputFormatChanged(format, /* decoderReuseEvaluation= */ null)); + handler.post( + () -> + eventListener.onVideoDecoderInitialized( + /* decoderName= */ "fake.video.decoder", + /* initializedTimestampMs= */ SystemClock.elapsedRealtime(), + /* initializationDurationMs= */ 0)); this.format = format; } @@ -131,10 +138,18 @@ public class FakeVideoRenderer extends FakeRenderer { @Nullable Object output = this.output; if (shouldProcess && !renderedFirstFrameAfterReset && output != null) { @MonotonicNonNull Format format = Assertions.checkNotNull(this.format); - eventDispatcher.videoSizeChanged( - new VideoSize( - format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio)); - eventDispatcher.renderedFirstFrame(output); + handler.post( + () -> + eventListener.onVideoSizeChanged( + new VideoSize( + format.width, + format.height, + format.rotationDegrees, + format.pixelWidthHeightRatio))); + handler.post( + () -> + eventListener.onRenderedFirstFrame( + output, /* renderTimeMs= */ SystemClock.elapsedRealtime())); renderedFirstFrameAfterReset = true; renderedFirstFrameAfterEnable = true; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.java index 034053d1b8..be5dc85eed 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayerBuilder.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -297,13 +298,16 @@ public class TestExoPlayerBuilder { videoRendererEventListener, audioRendererEventListener, textRendererOutput, - metadataRendererOutput) -> - renderers != null - ? renderers - : new Renderer[] { - new FakeVideoRenderer(eventHandler, videoRendererEventListener), - new FakeAudioRenderer(eventHandler, audioRendererEventListener) - }; + metadataRendererOutput) -> { + HandlerWrapper clockAwareHandler = + clock.createHandler(eventHandler.getLooper(), /* callback= */ null); + return renderers != null + ? renderers + : new Renderer[] { + new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener), + new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener) + }; + }; } ExoPlayer.Builder builder = From a68ab5f334cfe3e03a2d3ca0915267e5dbd67261 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Nov 2022 17:55:12 +0000 Subject: [PATCH 023/104] Clean up javadoc on `Metadata.Entry.populateMediaMetadata` Remove self-links, and remove section that is documenting internal ordering behaviour of [`SimpleBasePlayer.getCombinedMediaMetadata`](https://github.com/google/ExoPlayer/blob/bb270c62cf2f7a1570fe22f87bb348a2d5e94dcf/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java#L1770) rather than anything specifically about this method. #minor-release PiperOrigin-RevId: 490923719 (cherry picked from commit ed8c196e2eb36eefef9952d255cb6355807b9e9f) --- .../com/google/android/exoplayer2/metadata/Metadata.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java index 2e98e99f0e..9bbe0e8a95 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java @@ -52,11 +52,8 @@ public final class Metadata implements Parcelable { } /** - * Updates the {@link MediaMetadata.Builder} with the type specific values stored in this Entry. - * - *

    The order of the {@link Entry} objects in the {@link Metadata} matters. If two {@link - * Entry} entries attempt to populate the same {@link MediaMetadata} field, then the last one in - * the list is used. + * Updates the {@link MediaMetadata.Builder} with the type-specific values stored in this {@code + * Entry}. * * @param builder The builder to be updated. */ From 83b7f8085c33fa3e3c773d427b6ca7bc74fa4c5f Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 28 Nov 2022 09:25:18 +0000 Subject: [PATCH 024/104] Ensure messages sent on a dead thread don't block FakeClock execution FakeClock keeps an internal list of messages to be executed to ensure deterministic serialization. The next message from the list is triggered by a separate helper message sent to the real Handler. However, if the target HandlerThread is no longer alive (e.g. when it quit itself during the message execution), this helper message is never executed and the entire message execution chain is stuck forever. This can be solved by checking the return values of Hander.post or Handler.sendMessage, which are false if the message won't be delivered. If the messages are not delivered, we can unblock the chain by marking the message as complete and triggering the next one. PiperOrigin-RevId: 491275031 (cherry picked from commit 2977bef872d7f3a1611fd6e8a45931388ea21c9f) --- .../exoplayer2/testutil/FakeClock.java | 17 +++--- .../exoplayer2/testutil/FakeClockTest.java | 54 +++++++++++++++++-- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java index b9ae509619..0f8238f234 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java @@ -242,16 +242,19 @@ public class FakeClock implements Clock { } handlerMessages.remove(messageIndex); waitingForMessage = true; + boolean messageSent; + Handler realHandler = message.handler.handler; if (message.runnable != null) { - message.handler.handler.post(message.runnable); + messageSent = realHandler.post(message.runnable); } else { - message - .handler - .handler - .obtainMessage(message.what, message.arg1, message.arg2, message.obj) - .sendToTarget(); + messageSent = + realHandler.sendMessage( + realHandler.obtainMessage(message.what, message.arg1, message.arg2, message.obj)); + } + messageSent &= message.handler.internalHandler.post(this::onMessageHandled); + if (!messageSent) { + onMessageHandled(); } - message.handler.internalHandler.post(this::onMessageHandled); } private synchronized void onMessageHandled() { diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java index 7fba60ec24..7c1ff70310 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java @@ -29,6 +29,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.shadows.ShadowLooper; @@ -40,6 +41,7 @@ public final class FakeClockTest { @Test public void currentTimeMillis_withoutBootTime() { FakeClock fakeClock = new FakeClock(/* initialTimeMs= */ 10); + assertThat(fakeClock.currentTimeMillis()).isEqualTo(10); } @@ -48,6 +50,7 @@ public final class FakeClockTest { FakeClock fakeClock = new FakeClock( /* bootTimeMs= */ 150, /* initialTimeMs= */ 200, /* isAutoAdvancing= */ false); + assertThat(fakeClock.currentTimeMillis()).isEqualTo(350); } @@ -55,17 +58,24 @@ public final class FakeClockTest { public void currentTimeMillis_afterAdvanceTime_currentTimeHasAdvanced() { FakeClock fakeClock = new FakeClock(/* bootTimeMs= */ 100, /* initialTimeMs= */ 50, /* isAutoAdvancing= */ false); + fakeClock.advanceTime(/* timeDiffMs */ 250); + assertThat(fakeClock.currentTimeMillis()).isEqualTo(400); } @Test public void elapsedRealtime_afterAdvanceTime_timeHasAdvanced() { FakeClock fakeClock = new FakeClock(2000); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2000); + fakeClock.advanceTime(500); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500); + fakeClock.advanceTime(0); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500); } @@ -86,6 +96,7 @@ public final class FakeClockTest { .sendToTarget(); ShadowLooper.idleMainLooper(); shadowOf(handler.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(callback.messages) .containsExactly( @@ -126,6 +137,7 @@ public final class FakeClockTest { fakeClock.advanceTime(50); shadowOf(handler.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(callback.messages).hasSize(4); assertThat(Iterables.getLast(callback.messages)) @@ -146,6 +158,7 @@ public final class FakeClockTest { handler.obtainMessage(/* what= */ 4).sendToTarget(); ShadowLooper.idleMainLooper(); shadowOf(handler.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(callback.messages) .containsExactly( @@ -192,6 +205,8 @@ public final class FakeClockTest { fakeClock.advanceTime(1000); shadowOf(handler.getLooper()).idle(); assertTestRunnableStates(new boolean[] {true, true, true, true, true}, testRunnables); + + handlerThread.quitSafely(); } @Test @@ -203,7 +218,6 @@ public final class FakeClockTest { HandlerWrapper handler = fakeClock.createHandler(handlerThread.getLooper(), callback); TestCallback otherCallback = new TestCallback(); HandlerWrapper otherHandler = fakeClock.createHandler(handlerThread.getLooper(), otherCallback); - TestRunnable testRunnable1 = new TestRunnable(); TestRunnable testRunnable2 = new TestRunnable(); Object messageToken = new Object(); @@ -216,10 +230,10 @@ public final class FakeClockTest { handler.removeMessages(/* what= */ 2); handler.removeCallbacksAndMessages(messageToken); - fakeClock.advanceTime(50); ShadowLooper.idleMainLooper(); shadowOf(handlerThread.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(callback.messages) .containsExactly( @@ -242,7 +256,6 @@ public final class FakeClockTest { HandlerWrapper handler = fakeClock.createHandler(handlerThread.getLooper(), callback); TestCallback otherCallback = new TestCallback(); HandlerWrapper otherHandler = fakeClock.createHandler(handlerThread.getLooper(), otherCallback); - TestRunnable testRunnable1 = new TestRunnable(); TestRunnable testRunnable2 = new TestRunnable(); Object messageToken = new Object(); @@ -254,15 +267,14 @@ public final class FakeClockTest { otherHandler.sendEmptyMessage(/* what= */ 1); handler.removeCallbacksAndMessages(/* token= */ null); - fakeClock.advanceTime(50); ShadowLooper.idleMainLooper(); shadowOf(handlerThread.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(callback.messages).isEmpty(); assertThat(testRunnable1.hasRun).isFalse(); assertThat(testRunnable2.hasRun).isFalse(); - // Assert that message on other handler wasn't removed. assertThat(otherCallback.messages) .containsExactly( @@ -295,6 +307,7 @@ public final class FakeClockTest { }); ShadowLooper.idleMainLooper(); shadowOf(handler.getLooper()).idle(); + handlerThread.quitSafely(); assertThat(clockTimes).containsExactly(0L, 20L, 50L, 70L, 100L).inOrder(); } @@ -333,6 +346,8 @@ public final class FakeClockTest { }); ShadowLooper.idleMainLooper(); messagesFinished.block(); + handlerThread1.quitSafely(); + handlerThread2.quitSafely(); assertThat(executionOrder).containsExactly(1, 2, 3, 4, 5, 6, 7, 8).inOrder(); } @@ -368,10 +383,39 @@ public final class FakeClockTest { ShadowLooper.idleMainLooper(); shadowOf(handler1.getLooper()).idle(); shadowOf(handler2.getLooper()).idle(); + handlerThread1.quitSafely(); + handlerThread2.quitSafely(); assertThat(executionOrder).containsExactly(1, 2, 3, 4).inOrder(); } + @Test + public void createHandler_messageOnDeadThread_doesNotBlockExecution() { + HandlerThread handlerThread1 = new HandlerThread("FakeClockTest"); + handlerThread1.start(); + HandlerThread handlerThread2 = new HandlerThread("FakeClockTest"); + handlerThread2.start(); + FakeClock fakeClock = new FakeClock(/* initialTimeMs= */ 0); + HandlerWrapper handler1 = + fakeClock.createHandler(handlerThread1.getLooper(), /* callback= */ null); + HandlerWrapper handler2 = + fakeClock.createHandler(handlerThread2.getLooper(), /* callback= */ null); + + ConditionVariable messagesFinished = new ConditionVariable(); + AtomicBoolean messageOnDeadThreadExecuted = new AtomicBoolean(); + handler1.post( + () -> { + handlerThread1.quitSafely(); + handler1.post(() -> messageOnDeadThreadExecuted.set(true)); + handler2.post(messagesFinished::open); + }); + ShadowLooper.idleMainLooper(); + messagesFinished.block(); + handlerThread2.quitSafely(); + + assertThat(messageOnDeadThreadExecuted.get()).isFalse(); + } + private static void assertTestRunnableStates(boolean[] states, TestRunnable[] testRunnables) { for (int i = 0; i < testRunnables.length; i++) { assertThat(testRunnables[i].hasRun).isEqualTo(states[i]); From cbc25470433d1b6281fde1023941884f23b02061 Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 29 Nov 2022 19:04:05 +0000 Subject: [PATCH 025/104] Merge pull request #10799 from OxygenCobalt:id3v2-multi-value PiperOrigin-RevId: 491289028 (cherry picked from commit c827e46c3db2691b1b5fed57fa5b67890331aa85) --- .../ImaServerSideAdInsertionMediaSource.java | 2 +- .../android/exoplayer2/ExoPlayerTest.java | 4 +- .../metadata/MetadataRendererTest.java | 2 +- .../extractor/mp3/Mp3Extractor.java | 2 +- .../extractor/mp4/MetadataUtil.java | 12 ++- .../exoplayer2/metadata/id3/Id3Decoder.java | 53 ++++++++---- .../metadata/id3/TextInformationFrame.java | 81 +++++++++++++------ .../metadata/id3/ChapterFrameTest.java | 3 +- .../metadata/id3/ChapterTocFrameTest.java | 3 +- .../metadata/id3/Id3DecoderTest.java | 32 ++++++-- .../id3/TextInformationFrameTest.java | 77 +++++++++++++++--- .../flac/bear_with_id3_enabled_flac.0.dump | 2 +- .../flac/bear_with_id3_enabled_flac.1.dump | 2 +- .../flac/bear_with_id3_enabled_flac.2.dump | 2 +- .../flac/bear_with_id3_enabled_flac.3.dump | 2 +- ..._with_id3_enabled_flac.unknown_length.dump | 2 +- .../flac/bear_with_id3_enabled_raw.0.dump | 2 +- .../flac/bear_with_id3_enabled_raw.1.dump | 2 +- .../flac/bear_with_id3_enabled_raw.2.dump | 2 +- .../flac/bear_with_id3_enabled_raw.3.dump | 2 +- ...r_with_id3_enabled_raw.unknown_length.dump | 2 +- .../mp3/bear-id3-enabled.0.dump | 2 +- .../mp3/bear-id3-enabled.1.dump | 2 +- .../mp3/bear-id3-enabled.2.dump | 2 +- .../mp3/bear-id3-enabled.3.dump | 2 +- .../mp3/bear-id3-enabled.unknown_length.dump | 2 +- .../mp3/bear-vbr-xing-header.mp3.0.dump | 2 +- .../mp3/bear-vbr-xing-header.mp3.1.dump | 2 +- .../mp3/bear-vbr-xing-header.mp3.2.dump | 2 +- .../mp3/bear-vbr-xing-header.mp3.3.dump | 2 +- ...ar-vbr-xing-header.mp3.unknown_length.dump | 2 +- .../extractordumps/mp4/sample.mp4.0.dump | 2 +- .../extractordumps/mp4/sample.mp4.1.dump | 2 +- .../extractordumps/mp4/sample.mp4.2.dump | 2 +- .../extractordumps/mp4/sample.mp4.3.dump | 2 +- .../mp4/sample.mp4.unknown_length.dump | 2 +- .../mp4/sample_mdat_too_long.mp4.0.dump | 2 +- .../mp4/sample_mdat_too_long.mp4.1.dump | 2 +- .../mp4/sample_mdat_too_long.mp4.2.dump | 2 +- .../mp4/sample_mdat_too_long.mp4.3.dump | 2 +- ...mple_mdat_too_long.mp4.unknown_length.dump | 2 +- .../extractordumps/mp4/sample_opus.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_opus.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_opus.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_opus.mp4.3.dump | 2 +- .../mp4/sample_opus.mp4.unknown_length.dump | 2 +- .../sample_with_colr_mdcv_and_clli.mp4.0.dump | 2 +- .../sample_with_colr_mdcv_and_clli.mp4.1.dump | 2 +- .../sample_with_colr_mdcv_and_clli.mp4.2.dump | 2 +- .../sample_with_colr_mdcv_and_clli.mp4.3.dump | 2 +- ...colr_mdcv_and_clli.mp4.unknown_length.dump | 2 +- .../transformerdumps/mp4/sample.mp4.dump | 2 +- .../mp4/sample.mp4.novideo.dump | 2 +- ...sing_timestamps_320w_240h.mp4.clipped.dump | 2 +- 54 files changed, 245 insertions(+), 112 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index 9a94c53edf..82762f1df3 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -811,7 +811,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou if (entry instanceof TextInformationFrame) { TextInformationFrame textFrame = (TextInformationFrame) entry; if ("TXXX".equals(textFrame.id)) { - streamPlayer.triggerUserTextReceived(textFrame.value); + streamPlayer.triggerUserTextReceived(textFrame.values.get(0)); } } else if (entry instanceof EventMessage) { EventMessage eventMessage = (EventMessage) entry; 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 0af7aa0def..f60857e2a6 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 @@ -10398,7 +10398,9 @@ public final class ExoPlayerTest { new Metadata( new BinaryFrame(/* id= */ "", /* data= */ new byte[0]), new TextInformationFrame( - /* id= */ "TT2", /* description= */ null, /* value= */ "title"))) + /* id= */ "TT2", + /* description= */ null, + /* values= */ ImmutableList.of("title")))) .build(); // Set multiple values together. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java index d842f0dd48..8e971c0151 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java @@ -107,7 +107,7 @@ public class MetadataRendererTest { assertThat(metadata).hasSize(1); assertThat(metadata.get(0).length()).isEqualTo(1); TextInformationFrame expectedId3Frame = - new TextInformationFrame("TXXX", "Test description", "Test value"); + new TextInformationFrame("TXXX", "Test description", ImmutableList.of("Test value")); assertThat(metadata.get(0).get(0)).isEqualTo(expectedId3Frame); } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 245e99c2e4..40bf68e152 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -592,7 +592,7 @@ public final class Mp3Extractor implements Extractor { Metadata.Entry entry = metadata.get(i); if (entry instanceof TextInformationFrame && ((TextInformationFrame) entry).id.equals("TLEN")) { - return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).value)); + return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).values.get(0))); } } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index 9aa19cff56..9b9fea44ca 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.metadata.mp4.MdtaMetadataEntry; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.compatqual.NullableType; /** Utilities for handling metadata in MP4. */ @@ -452,7 +453,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (atomType == Atom.TYPE_data) { data.skipBytes(8); // version (1), flags (3), empty (4) String value = data.readNullTerminatedString(atomSize - 16); - return new TextInformationFrame(id, /* description= */ null, value); + return new TextInformationFrame(id, /* description= */ null, ImmutableList.of(value)); } Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type)); return null; @@ -484,7 +485,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } if (value >= 0) { return isTextInformationFrame - ? new TextInformationFrame(id, /* description= */ null, Integer.toString(value)) + ? new TextInformationFrame( + id, /* description= */ null, ImmutableList.of(Integer.toString(value))) : new CommentFrame(C.LANGUAGE_UNDETERMINED, id, Integer.toString(value)); } Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); @@ -505,7 +507,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (count > 0) { value += "/" + count; } - return new TextInformationFrame(attributeName, /* description= */ null, value); + return new TextInformationFrame( + attributeName, /* description= */ null, ImmutableList.of(value)); } } Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type)); @@ -521,7 +524,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ? STANDARD_GENRES[genreCode - 1] : null; if (genreString != null) { - return new TextInformationFrame("TCON", /* description= */ null, genreString); + return new TextInformationFrame( + "TCON", /* description= */ null, ImmutableList.of(genreString)); } Log.w(TAG, "Failed to parse standard genre code"); return null; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 46bd482178..ccf9f2f715 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -455,14 +456,13 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); - int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); - String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); - - return new TextInformationFrame("TXXX", description, value); + ImmutableList values = + decodeTextInformationFrameValues( + data, encoding, descriptionEndIndex + delimiterLength(encoding)); + return new TextInformationFrame("TXXX", description, values); } @Nullable @@ -474,15 +474,34 @@ public final class Id3Decoder extends SimpleMetadataDecoder { } int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int valueEndIndex = indexOfEos(data, 0, encoding); - String value = new String(data, 0, valueEndIndex, charset); + ImmutableList values = decodeTextInformationFrameValues(data, encoding, 0); + return new TextInformationFrame(id, null, values); + } - return new TextInformationFrame(id, null, value); + private static ImmutableList decodeTextInformationFrameValues( + byte[] data, final int encoding, final int index) throws UnsupportedEncodingException { + if (index >= data.length) { + return ImmutableList.of(""); + } + + ImmutableList.Builder values = ImmutableList.builder(); + String charset = getCharsetName(encoding); + int valueStartIndex = index; + int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); + while (valueStartIndex < valueEndIndex) { + String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); + values.add(value); + + valueStartIndex = valueEndIndex + delimiterLength(encoding); + valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); + } + + ImmutableList result = values.build(); + return result.isEmpty() ? ImmutableList.of("") : result; } @Nullable @@ -499,7 +518,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); @@ -546,11 +565,11 @@ public final class Id3Decoder extends SimpleMetadataDecoder { String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); int filenameStartIndex = mimeTypeEndIndex + 1; - int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); + int filenameEndIndex = indexOfTerminator(data, filenameStartIndex, encoding); String filename = decodeStringIfValid(data, filenameStartIndex, filenameEndIndex, charset); int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = decodeStringIfValid(data, descriptionStartIndex, descriptionEndIndex, charset); @@ -588,7 +607,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; int descriptionStartIndex = mimeTypeEndIndex + 2; - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = new String( data, descriptionStartIndex, descriptionEndIndex - descriptionStartIndex, charset); @@ -617,11 +636,11 @@ public final class Id3Decoder extends SimpleMetadataDecoder { data = new byte[frameSize - 4]; id3Data.readBytes(data, 0, frameSize - 4); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int textStartIndex = descriptionEndIndex + delimiterLength(encoding); - int textEndIndex = indexOfEos(data, textStartIndex, encoding); + int textEndIndex = indexOfTerminator(data, textStartIndex, encoding); String text = decodeStringIfValid(data, textStartIndex, textEndIndex, charset); return new CommentFrame(language, description, text); @@ -798,7 +817,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); } - private static int indexOfEos(byte[] data, int fromIndex, int encoding) { + private static int indexOfTerminator(byte[] data, int fromIndex, int encoding) { int terminationPos = indexOfZeroByte(data, fromIndex); // For single byte encoding charsets, we're done. diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index 7bab697331..8b46b1a56c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -15,13 +15,16 @@ */ package com.google.android.exoplayer2.metadata.id3; -import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.InlineMe; import java.util.ArrayList; import java.util.List; @@ -29,42 +32,69 @@ import java.util.List; public final class TextInformationFrame extends Id3Frame { @Nullable public final String description; - public final String value; - public TextInformationFrame(String id, @Nullable String description, String value) { + /** + * @deprecated Use the first element of {@link #values} instead. + */ + @Deprecated public final String value; + + /** The text values of this frame. Will always have at least one element. */ + public final ImmutableList values; + + public TextInformationFrame(String id, @Nullable String description, List values) { super(id); + checkArgument(!values.isEmpty()); + this.description = description; - this.value = value; + this.values = ImmutableList.copyOf(values); + this.value = this.values.get(0); } - /* package */ TextInformationFrame(Parcel in) { - super(castNonNull(in.readString())); - description = in.readString(); - value = castNonNull(in.readString()); + /** + * @deprecated Use {@code TextInformationFrame(String id, String description, String[] values} + * instead + */ + @Deprecated + @InlineMe( + replacement = "this(id, description, ImmutableList.of(value))", + imports = "com.google.common.collect.ImmutableList") + public TextInformationFrame(String id, @Nullable String description, String value) { + this(id, description, ImmutableList.of(value)); } + private TextInformationFrame(Parcel in) { + this( + checkNotNull(in.readString()), + in.readString(), + ImmutableList.copyOf(checkNotNull(in.createStringArray()))); + } + + /** + * Uses the first element in {@link #values} to set the relevant field in {@link MediaMetadata} + * (as determined by {@link #id}). + */ @Override public void populateMediaMetadata(MediaMetadata.Builder builder) { switch (id) { case "TT2": case "TIT2": - builder.setTitle(value); + builder.setTitle(values.get(0)); break; case "TP1": case "TPE1": - builder.setArtist(value); + builder.setArtist(values.get(0)); break; case "TP2": case "TPE2": - builder.setAlbumArtist(value); + builder.setAlbumArtist(values.get(0)); break; case "TAL": case "TALB": - builder.setAlbumTitle(value); + builder.setAlbumTitle(values.get(0)); break; case "TRK": case "TRCK": - String[] trackNumbers = Util.split(value, "/"); + String[] trackNumbers = Util.split(values.get(0), "/"); try { int trackNumber = Integer.parseInt(trackNumbers[0]); @Nullable @@ -78,7 +108,7 @@ public final class TextInformationFrame extends Id3Frame { case "TYE": case "TYER": try { - builder.setRecordingYear(Integer.parseInt(value)); + builder.setRecordingYear(Integer.parseInt(values.get(0))); } catch (NumberFormatException e) { // Do nothing, invalid input. } @@ -86,15 +116,16 @@ public final class TextInformationFrame extends Id3Frame { case "TDA": case "TDAT": try { - int month = Integer.parseInt(value.substring(2, 4)); - int day = Integer.parseInt(value.substring(0, 2)); + String date = values.get(0); + int month = Integer.parseInt(date.substring(2, 4)); + int day = Integer.parseInt(date.substring(0, 2)); builder.setRecordingMonth(month).setRecordingDay(day); } catch (NumberFormatException | StringIndexOutOfBoundsException e) { // Do nothing, invalid input. } break; case "TDRC": - List recordingDate = parseId3v2point4TimestampFrameForDate(value); + List recordingDate = parseId3v2point4TimestampFrameForDate(values.get(0)); switch (recordingDate.size()) { case 3: builder.setRecordingDay(recordingDate.get(2)); @@ -112,7 +143,7 @@ public final class TextInformationFrame extends Id3Frame { } break; case "TDRL": - List releaseDate = parseId3v2point4TimestampFrameForDate(value); + List releaseDate = parseId3v2point4TimestampFrameForDate(values.get(0)); switch (releaseDate.size()) { case 3: builder.setReleaseDay(releaseDate.get(2)); @@ -131,15 +162,15 @@ public final class TextInformationFrame extends Id3Frame { break; case "TCM": case "TCOM": - builder.setComposer(value); + builder.setComposer(values.get(0)); break; case "TP3": case "TPE3": - builder.setConductor(value); + builder.setConductor(values.get(0)); break; case "TXT": case "TEXT": - builder.setWriter(value); + builder.setWriter(values.get(0)); break; default: break; @@ -157,7 +188,7 @@ public final class TextInformationFrame extends Id3Frame { TextInformationFrame other = (TextInformationFrame) obj; return Util.areEqual(id, other.id) && Util.areEqual(description, other.description) - && Util.areEqual(value, other.value); + && values.equals(other.values); } @Override @@ -165,13 +196,13 @@ public final class TextInformationFrame extends Id3Frame { int result = 17; result = 31 * result + id.hashCode(); result = 31 * result + (description != null ? description.hashCode() : 0); - result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + values.hashCode(); return result; } @Override public String toString() { - return id + ": description=" + description + ": value=" + value; + return id + ": description=" + description + ": values=" + values; } // Parcelable implementation. @@ -180,7 +211,7 @@ public final class TextInformationFrame extends Id3Frame { public void writeToParcel(Parcel dest, int flags) { dest.writeString(id); dest.writeString(description); - dest.writeString(value); + dest.writeStringArray(values.toArray(new String[0])); } public static final Parcelable.Creator CREATOR = diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java index 25d6ceb780..fc5e4795ca 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +31,7 @@ public final class ChapterFrameTest { public void parcelable() { Id3Frame[] subFrames = new Id3Frame[] { - new TextInformationFrame("TIT2", null, "title"), + new TextInformationFrame("TIT2", null, ImmutableList.of("title")), new UrlLinkFrame("WXXX", "description", "url") }; ChapterFrame chapterFrameToParcel = new ChapterFrame("id", 0, 1, 2, 3, subFrames); diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java index f0f8b41b70..d5b4fd47eb 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +32,7 @@ public final class ChapterTocFrameTest { String[] children = new String[] {"child0", "child1"}; Id3Frame[] subFrames = new Id3Frame[] { - new TextInformationFrame("TIT2", null, "title"), + new TextInformationFrame("TIT2", null, ImmutableList.of("title")), new UrlLinkFrame("WXXX", "description", "url") }; ChapterTocFrame chapterTocFrameToParcel = diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 833a290cea..40c8c74d18 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -52,7 +52,7 @@ public final class Id3DecoderTest { TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); - assertThat(textInformationFrame.value).isEqualTo("mdialog_VINDICO1527664_start"); + assertThat(textInformationFrame.values.get(0)).isEqualTo("mdialog_VINDICO1527664_start"); // Test UTF-16. rawId3 = @@ -67,7 +67,21 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEqualTo("Hello World"); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).containsExactly(""); + + // Test multiple values. + rawId3 = + buildSingleFrameTag( + "TXXX", + new byte[] { + 1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, + 100, 0, 0, 0, 70, 0, 111, 0, 111, 0, 0, 0, 66, 0, 97, 0, 114, 0, 0 + }); + metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); + textInformationFrame = (TextInformationFrame) metadata.get(0); + assertThat(textInformationFrame.description).isEqualTo("Hello World"); + assertThat(textInformationFrame.values).containsExactly("Foo", "Bar").inOrder(); // Test empty. rawId3 = buildSingleFrameTag("TXXX", new byte[0]); @@ -81,7 +95,7 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).containsExactly(""); } @Test @@ -95,7 +109,15 @@ public final class Id3DecoderTest { TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); - assertThat(textInformationFrame.value).isEqualTo("Hello World"); + assertThat(textInformationFrame.values.size()).isEqualTo(1); + assertThat(textInformationFrame.values.get(0)).isEqualTo("Hello World"); + + // Test multiple values. + rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 70, 111, 111, 0, 66, 97, 114, 0}); + metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); + textInformationFrame = (TextInformationFrame) metadata.get(0); + assertThat(textInformationFrame.values).containsExactly("Foo", "Bar").inOrder(); // Test empty. rawId3 = buildSingleFrameTag("TIT2", new byte[0]); @@ -109,7 +131,7 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).containsExactly(""); } @Test diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrameTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrameTest.java index 00fdcb6087..6f84017ab3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrameTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrameTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.metadata.id3; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -32,7 +33,8 @@ public class TextInformationFrameTest { @Test public void parcelable() { - TextInformationFrame textInformationFrameToParcel = new TextInformationFrame("", "", ""); + TextInformationFrame textInformationFrameToParcel = + new TextInformationFrame("", "", ImmutableList.of("")); Parcel parcel = Parcel.obtain(); textInformationFrameToParcel.writeToParcel(parcel, 0); @@ -62,28 +64,42 @@ public class TextInformationFrameTest { List entries = ImmutableList.of( - new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ title), - new TextInformationFrame(/* id= */ "TP1", /* description= */ null, /* value= */ artist), new TextInformationFrame( - /* id= */ "TAL", /* description= */ null, /* value= */ albumTitle), + /* id= */ "TT2", /* description= */ null, /* values= */ ImmutableList.of(title)), new TextInformationFrame( - /* id= */ "TP2", /* description= */ null, /* value= */ albumArtist), + /* id= */ "TP1", /* description= */ null, /* values= */ ImmutableList.of(artist)), new TextInformationFrame( - /* id= */ "TRK", /* description= */ null, /* value= */ trackNumberInfo), + /* id= */ "TAL", + /* description= */ null, + /* values= */ ImmutableList.of(albumTitle)), new TextInformationFrame( - /* id= */ "TYE", /* description= */ null, /* value= */ recordingYear), + /* id= */ "TP2", + /* description= */ null, + /* values= */ ImmutableList.of(albumArtist)), + new TextInformationFrame( + /* id= */ "TRK", + /* description= */ null, + /* values= */ ImmutableList.of(trackNumberInfo)), + new TextInformationFrame( + /* id= */ "TYE", + /* description= */ null, + /* values= */ ImmutableList.of(recordingYear)), new TextInformationFrame( /* id= */ "TDA", /* description= */ null, - /* value= */ recordingDay + recordingMonth), + /* values= */ ImmutableList.of(recordingDay + recordingMonth)), new TextInformationFrame( - /* id= */ "TDRL", /* description= */ null, /* value= */ releaseDate), + /* id= */ "TDRL", + /* description= */ null, + /* values= */ ImmutableList.of(releaseDate)), new TextInformationFrame( - /* id= */ "TCM", /* description= */ null, /* value= */ composer), + /* id= */ "TCM", /* description= */ null, /* values= */ ImmutableList.of(composer)), new TextInformationFrame( - /* id= */ "TP3", /* description= */ null, /* value= */ conductor), + /* id= */ "TP3", + /* description= */ null, + /* values= */ ImmutableList.of(conductor)), new TextInformationFrame( - /* id= */ "TXT", /* description= */ null, /* value= */ writer)); + /* id= */ "TXT", /* description= */ null, /* values= */ ImmutableList.of(writer))); MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon(); for (Metadata.Entry entry : entries) { @@ -108,4 +124,41 @@ public class TextInformationFrameTest { assertThat(mediaMetadata.conductor.toString()).isEqualTo(conductor); assertThat(mediaMetadata.writer.toString()).isEqualTo(writer); } + + @Test + public void emptyValuesListThrowsException() { + assertThrows( + IllegalArgumentException.class, + () -> new TextInformationFrame("TXXX", "description", ImmutableList.of())); + } + + @Test + @SuppressWarnings("deprecation") // Testing deprecated field + public void deprecatedValueStillPopulated() { + TextInformationFrame frame = + new TextInformationFrame("TXXX", "description", ImmutableList.of("value")); + + assertThat(frame.value).isEqualTo("value"); + assertThat(frame.values).containsExactly("value"); + } + + @Test + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // Testing deprecated constructor + public void deprecatedConstructorPopulatesValuesList() { + TextInformationFrame frame = new TextInformationFrame("TXXX", "description", "value"); + + assertThat(frame.value).isEqualTo("value"); + assertThat(frame.values).containsExactly("value"); + } + + @Test + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // Testing deprecated constructor + public void deprecatedConstructorCreatesEqualInstance() { + TextInformationFrame frame1 = new TextInformationFrame("TXXX", "description", "value"); + TextInformationFrame frame2 = + new TextInformationFrame("TXXX", "description", ImmutableList.of("value")); + + assertThat(frame1).isEqualTo(frame2); + assertThat(frame1.hashCode()).isEqualTo(frame2.hashCode()); + } } diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.0.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.0.dump index d2a8d6a442..b6a9c0947d 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.0.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.0.dump @@ -14,7 +14,7 @@ track 0: maxInputSize = 5776 channelCount = 2 sampleRate = 48000 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] initializationData: data = length 42, hash 83F6895 sample 0: diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.1.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.1.dump index 250d1add95..725c496cec 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.1.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.1.dump @@ -14,7 +14,7 @@ track 0: maxInputSize = 5776 channelCount = 2 sampleRate = 48000 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] initializationData: data = length 42, hash 83F6895 sample 0: diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.2.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.2.dump index e5057cff25..c310e1ffdc 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.2.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.2.dump @@ -14,7 +14,7 @@ track 0: maxInputSize = 5776 channelCount = 2 sampleRate = 48000 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] initializationData: data = length 42, hash 83F6895 sample 0: diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.3.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.3.dump index afaead1d88..1423a1df78 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.3.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.3.dump @@ -14,7 +14,7 @@ track 0: maxInputSize = 5776 channelCount = 2 sampleRate = 48000 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] initializationData: data = length 42, hash 83F6895 sample 0: diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.unknown_length.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.unknown_length.dump index d2a8d6a442..b6a9c0947d 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_flac.unknown_length.dump @@ -14,7 +14,7 @@ track 0: maxInputSize = 5776 channelCount = 2 sampleRate = 48000 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] initializationData: data = length 42, hash 83F6895 sample 0: diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.0.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.0.dump index ca9f1a74a1..68f3fe89a6 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.0.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.0.dump @@ -17,7 +17,7 @@ track 0: channelCount = 2 sampleRate = 48000 pcmEncoding = 2 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.1.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.1.dump index 36314d9433..364d99e3cc 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.1.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.1.dump @@ -17,7 +17,7 @@ track 0: channelCount = 2 sampleRate = 48000 pcmEncoding = 2 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] sample 0: time = 853333 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.2.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.2.dump index 0e8cc73341..ce1cee5dd2 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.2.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.2.dump @@ -17,7 +17,7 @@ track 0: channelCount = 2 sampleRate = 48000 pcmEncoding = 2 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] sample 0: time = 1792000 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.3.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.3.dump index 8ef6f9cb33..0d23e7d7db 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.3.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.3.dump @@ -17,7 +17,7 @@ track 0: channelCount = 2 sampleRate = 48000 pcmEncoding = 2 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] sample 0: time = 2645333 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.unknown_length.dump b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.unknown_length.dump index ca9f1a74a1..68f3fe89a6 100644 --- a/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/flac/bear_with_id3_enabled_raw.unknown_length.dump @@ -17,7 +17,7 @@ track 0: channelCount = 2 sampleRate = 48000 pcmEncoding = 2 - metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=] + metadata = entries=[TXXX: description=ID: values=[105519843], TIT2: description=null: values=[那么爱你为什么], TPE1: description=null: values=[阿强], TALB: description=null: values=[华丽的外衣], TXXX: description=ID: values=[105519843], APIC: mimeType=image/jpeg, description=] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.0.dump b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.0.dump index c252057e47..84b9b78db4 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.0.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.0.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] + metadata = entries=[TIT2: description=null: values=[Test title], TPE1: description=null: values=[Test Artist], TALB: description=null: values=[Test Album], TXXX: description=Test description: values=[Test user info], COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: values=[Lavf58.29.100], MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.1.dump b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.1.dump index 76fcbc0f8e..3c0e31eb8e 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.1.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.1.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] + metadata = entries=[TIT2: description=null: values=[Test title], TPE1: description=null: values=[Test Artist], TALB: description=null: values=[Test Album], TXXX: description=Test description: values=[Test user info], COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: values=[Lavf58.29.100], MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] sample 0: time = 943000 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.2.dump b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.2.dump index 4f9b29dc55..1e877253dc 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.2.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.2.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] + metadata = entries=[TIT2: description=null: values=[Test title], TPE1: description=null: values=[Test Artist], TALB: description=null: values=[Test Album], TXXX: description=Test description: values=[Test user info], COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: values=[Lavf58.29.100], MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] sample 0: time = 1879000 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.3.dump b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.3.dump index 220965634f..702eefce2e 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.3.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.3.dump @@ -16,5 +16,5 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] + metadata = entries=[TIT2: description=null: values=[Test title], TPE1: description=null: values=[Test Artist], TALB: description=null: values=[Test Album], TXXX: description=Test description: values=[Test user info], COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: values=[Lavf58.29.100], MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.unknown_length.dump index c252057e47..84b9b78db4 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-id3-enabled.unknown_length.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] + metadata = entries=[TIT2: description=null: values=[Test title], TPE1: description=null: values=[Test Artist], TALB: description=null: values=[Test Album], TXXX: description=Test description: values=[Test user info], COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: values=[Lavf58.29.100], MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.0.dump b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.0.dump index 20a69e34a8..352641de04 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.0.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.0.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.1.dump b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.1.dump index ef3785beb3..811fd0aaaa 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.1.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.1.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] sample 0: time = 958041 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.2.dump b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.2.dump index 12697f6d12..7e2745f280 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.2.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.2.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] sample 0: time = 1886772 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.3.dump b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.3.dump index 2ab479e633..11102a27ce 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.3.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.3.dump @@ -16,5 +16,5 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.unknown_length.dump index 20a69e34a8..352641de04 100644 --- a/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp3/bear-vbr-xing-header.mp3.unknown_length.dump @@ -16,7 +16,7 @@ track 0: sampleRate = 48000 encoderDelay = 576 encoderPadding = 576 - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] sample 0: time = 0 flags = 1 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.0.dump index 804961f690..a2e644c980 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.0.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.1.dump index a974548364..621c343df1 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.1.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.2.dump index 19fd0f36d2..f5585c7bf6 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.2.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.3.dump index 80ca2a76c7..d94ad775db 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.3.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.unknown_length.dump index 804961f690..a2e644c980 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample.mp4.unknown_length.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.0.dump index 321bc3a832..1bcbd8e43f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.0.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.1.dump index 4d8fe681ce..2cb5ff29f5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.1.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.2.dump index a3e5cd60d0..bfe2e5b1b0 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.2.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.3.dump index a498d93b5e..f90a082a27 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.3.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.unknown_length.dump index 321bc3a832..1bcbd8e43f 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_mdat_too_long.mp4.unknown_length.dump @@ -152,7 +152,7 @@ track 1: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump index f9c1539402..fb6ca4f7d2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump @@ -16,7 +16,7 @@ track 0: channelCount = 2 sampleRate = 48000 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] initializationData: data = length 19, hash 86852AE2 data = length 8, hash 72CBCBF5 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump index d0c0ab1586..86625de6e0 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump @@ -16,7 +16,7 @@ track 0: channelCount = 2 sampleRate = 48000 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] initializationData: data = length 19, hash 86852AE2 data = length 8, hash 72CBCBF5 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.2.dump index cc688289e2..f63cc0a500 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.2.dump @@ -16,7 +16,7 @@ track 0: channelCount = 2 sampleRate = 48000 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] initializationData: data = length 19, hash 86852AE2 data = length 8, hash 72CBCBF5 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.3.dump index 42e14dbd2d..ec53664fc5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.3.dump @@ -16,7 +16,7 @@ track 0: channelCount = 2 sampleRate = 48000 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] initializationData: data = length 19, hash 86852AE2 data = length 8, hash 72CBCBF5 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump index f9c1539402..fb6ca4f7d2 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump @@ -16,7 +16,7 @@ track 0: channelCount = 2 sampleRate = 48000 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.29.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.29.100]] initializationData: data = length 19, hash 86852AE2 data = length 8, hash 72CBCBF5 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump index 8c1813ef83..54ba703ee5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump @@ -274,7 +274,7 @@ track 1: channelCount = 2 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 16, hash CAA21BBF sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump index 5011cfa353..4ae939fe5a 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump @@ -274,7 +274,7 @@ track 1: channelCount = 2 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 16, hash CAA21BBF sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump index ad7c5fbe40..ff45deced5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump @@ -274,7 +274,7 @@ track 1: channelCount = 2 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 16, hash CAA21BBF sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump index 9e8fbd7584..4a58490278 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump @@ -274,7 +274,7 @@ track 1: channelCount = 2 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 16, hash CAA21BBF sample 0: diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump index 8c1813ef83..54ba703ee5 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump @@ -274,7 +274,7 @@ track 1: channelCount = 2 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 16, hash CAA21BBF sample 0: diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump index be627cc4d4..2ee74557d5 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.dump @@ -7,7 +7,7 @@ format 0: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 format 1: diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump index 5ec2d5f904..b08fed3442 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample.mp4.novideo.dump @@ -7,7 +7,7 @@ format 0: channelCount = 1 sampleRate = 44100 language = und - metadata = entries=[TSSE: description=null: value=Lavf56.1.0] + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0]] initializationData: data = length 2, hash 5F7 sample: diff --git a/testdata/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4.clipped.dump b/testdata/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4.clipped.dump index 90f6bb0017..f37630c1cd 100644 --- a/testdata/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4.clipped.dump +++ b/testdata/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4.clipped.dump @@ -8,7 +8,7 @@ format 0: channelCount = 2 sampleRate = 48000 language = en - metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100]] initializationData: data = length 2, hash 560 format 1: From 50e686ea58a71189094400f04fe88723b58a803c Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 28 Nov 2022 11:33:36 +0000 Subject: [PATCH 026/104] Split up `Id3DecoderTest` methods It's clearer if each test method follows the Arrange/Act/Assert pattern PiperOrigin-RevId: 491299379 (cherry picked from commit bf77290fbe78aa25698c1a7082b2a18cd7f1b06c) --- .../metadata/id3/Id3DecoderTest.java | 198 ++++++++++++------ 1 file changed, 139 insertions(+), 59 deletions(-) diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 40c8c74d18..c86d1235a7 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -37,8 +37,7 @@ public final class Id3DecoderTest { private static final int ID3_TEXT_ENCODING_UTF_8 = 3; @Test - public void decodeTxxxFrame() { - // Test UTF-8. + public void decodeTxxxFrame_utf8() { byte[] rawId3 = buildSingleFrameTag( "TXXX", @@ -47,52 +46,74 @@ public final class Id3DecoderTest { 55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0 }); Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); assertThat(textInformationFrame.values.get(0)).isEqualTo("mdialog_VINDICO1527664_start"); + } - // Test UTF-16. - rawId3 = + @Test + public void decodeTxxxFrame_utf16() { + byte[] rawId3 = buildSingleFrameTag( "TXXX", new byte[] { 1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0 }); - metadata = decoder.decode(rawId3, rawId3.length); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); - textInformationFrame = (TextInformationFrame) metadata.get(0); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEqualTo("Hello World"); assertThat(textInformationFrame.values).containsExactly(""); + } - // Test multiple values. - rawId3 = + @Test + public void decodeTxxxFrame_multipleValues() { + byte[] rawId3 = buildSingleFrameTag( "TXXX", new byte[] { 1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0, 0, 70, 0, 111, 0, 111, 0, 0, 0, 66, 0, 97, 0, 114, 0, 0 }); - metadata = decoder.decode(rawId3, rawId3.length); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); - textInformationFrame = (TextInformationFrame) metadata.get(0); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.description).isEqualTo("Hello World"); assertThat(textInformationFrame.values).containsExactly("Foo", "Bar").inOrder(); + } + + @Test + public void decodeTxxxFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("TXXX", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("TXXX", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(0); + } + + @Test + public void decodeTxxxFrame_encodingByteOnly() { + byte[] rawId3 = buildSingleFrameTag("TXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test encoding byte only. - rawId3 = buildSingleFrameTag("TXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - textInformationFrame = (TextInformationFrame) metadata.get(0); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); assertThat(textInformationFrame.values).containsExactly(""); @@ -104,31 +125,49 @@ public final class Id3DecoderTest { buildSingleFrameTag( "TIT2", new byte[] {3, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0}); Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); assertThat(textInformationFrame.values.size()).isEqualTo(1); assertThat(textInformationFrame.values.get(0)).isEqualTo("Hello World"); + } + @Test + public void decodeTextInformationFrame_multipleValues() { // Test multiple values. - rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 70, 111, 111, 0, 66, 97, 114, 0}); - metadata = decoder.decode(rawId3, rawId3.length); + byte[] rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 70, 111, 111, 0, 66, 97, 114, 0}); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); - textInformationFrame = (TextInformationFrame) metadata.get(0); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.values).containsExactly("Foo", "Bar").inOrder(); + } + + @Test + public void decodeTextInformationFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("TIT2", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("TIT2", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(0); + } + + @Test + public void decodeTextInformationFrame_encodingByteOnly() { + byte[] rawId3 = buildSingleFrameTag("TIT2", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test encoding byte only. - rawId3 = buildSingleFrameTag("TIT2", new byte[] {ID3_TEXT_ENCODING_UTF_8}); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - textInformationFrame = (TextInformationFrame) metadata.get(0); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); assertThat(textInformationFrame.values).containsExactly(""); @@ -172,23 +211,35 @@ public final class Id3DecoderTest { 102 }); Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); UrlLinkFrame urlLinkFrame = (UrlLinkFrame) metadata.get(0); assertThat(urlLinkFrame.id).isEqualTo("WXXX"); assertThat(urlLinkFrame.description).isEqualTo("test"); assertThat(urlLinkFrame.url).isEqualTo("https://test.com/abc?def"); + } + + @Test + public void decodeWxxxFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("WXXX", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("WXXX", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(0); + } + + @Test + public void decodeWxxxFrame_encodingByteOnly() { + byte[] rawId3 = buildSingleFrameTag("WXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test encoding byte only. - rawId3 = buildSingleFrameTag("WXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - urlLinkFrame = (UrlLinkFrame) metadata.get(0); + UrlLinkFrame urlLinkFrame = (UrlLinkFrame) metadata.get(0); assertThat(urlLinkFrame.id).isEqualTo("WXXX"); assertThat(urlLinkFrame.description).isEmpty(); assertThat(urlLinkFrame.url).isEmpty(); @@ -210,12 +261,17 @@ public final class Id3DecoderTest { assertThat(urlLinkFrame.id).isEqualTo("WCOM"); assertThat(urlLinkFrame.description).isNull(); assertThat(urlLinkFrame.url).isEqualTo("https://test.com/abc?def"); + } + + @Test + public void decodeUrlLinkFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("WCOM", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("WCOM", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - urlLinkFrame = (UrlLinkFrame) metadata.get(0); + UrlLinkFrame urlLinkFrame = (UrlLinkFrame) metadata.get(0); assertThat(urlLinkFrame.id).isEqualTo("WCOM"); assertThat(urlLinkFrame.description).isNull(); assertThat(urlLinkFrame.url).isEmpty(); @@ -230,12 +286,17 @@ public final class Id3DecoderTest { PrivFrame privFrame = (PrivFrame) metadata.get(0); assertThat(privFrame.owner).isEqualTo("test"); assertThat(privFrame.privateData).isEqualTo(new byte[] {1, 2, 3, 4}); + } + + @Test + public void decodePrivFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("PRIV", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("PRIV", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - privFrame = (PrivFrame) metadata.get(0); + PrivFrame privFrame = (PrivFrame) metadata.get(0); assertThat(privFrame.owner).isEmpty(); assertThat(privFrame.privateData).isEqualTo(new byte[0]); } @@ -258,9 +319,11 @@ public final class Id3DecoderTest { assertThat(apicFrame.description).isEqualTo("Hello World"); assertThat(apicFrame.pictureData).hasLength(10); assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); + } - // Test with UTF-16 description at even offset. - rawId3 = + @Test + public void decodeApicFrame_utf16DescriptionEvenOffset() { + byte[] rawId3 = buildSingleFrameTag( "APIC", new byte[] { @@ -268,28 +331,34 @@ public final class Id3DecoderTest { 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }); - decoder = new Id3Decoder(); - metadata = decoder.decode(rawId3, rawId3.length); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); - apicFrame = (ApicFrame) metadata.get(0); + ApicFrame apicFrame = (ApicFrame) metadata.get(0); assertThat(apicFrame.mimeType).isEqualTo("image/jpeg"); assertThat(apicFrame.pictureType).isEqualTo(16); assertThat(apicFrame.description).isEqualTo("Hello World"); assertThat(apicFrame.pictureData).hasLength(10); assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); + } - // Test with UTF-16 description at odd offset. - rawId3 = + @Test + public void decodeApicFrame_utf16DescriptionOddOffset() { + byte[] rawId3 = buildSingleFrameTag( "APIC", new byte[] { 1, 105, 109, 97, 103, 101, 47, 112, 110, 103, 0, 16, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }); - decoder = new Id3Decoder(); - metadata = decoder.decode(rawId3, rawId3.length); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); - apicFrame = (ApicFrame) metadata.get(0); + ApicFrame apicFrame = (ApicFrame) metadata.get(0); assertThat(apicFrame.mimeType).isEqualTo("image/png"); assertThat(apicFrame.pictureType).isEqualTo(16); assertThat(apicFrame.description).isEqualTo("Hello World"); @@ -332,17 +401,28 @@ public final class Id3DecoderTest { assertThat(commentFrame.language).isEqualTo("eng"); assertThat(commentFrame.description).isEqualTo("description"); assertThat(commentFrame.text).isEqualTo("text"); + } + + @Test + public void decodeCommentFrame_empty() { + byte[] rawId3 = buildSingleFrameTag("COMM", new byte[0]); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test empty. - rawId3 = buildSingleFrameTag("COMM", new byte[0]); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(0); + } + + @Test + public void decodeCommentFrame_languageOnly() { + byte[] rawId3 = + buildSingleFrameTag("COMM", new byte[] {ID3_TEXT_ENCODING_UTF_8, 101, 110, 103}); + Id3Decoder decoder = new Id3Decoder(); + + Metadata metadata = decoder.decode(rawId3, rawId3.length); - // Test language only. - rawId3 = buildSingleFrameTag("COMM", new byte[] {ID3_TEXT_ENCODING_UTF_8, 101, 110, 103}); - metadata = decoder.decode(rawId3, rawId3.length); assertThat(metadata.length()).isEqualTo(1); - commentFrame = (CommentFrame) metadata.get(0); + CommentFrame commentFrame = (CommentFrame) metadata.get(0); assertThat(commentFrame.language).isEqualTo("eng"); assertThat(commentFrame.description).isEmpty(); assertThat(commentFrame.text).isEmpty(); From 70a55ef7e951cbfcbde8158f608e034fe181f986 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 28 Nov 2022 14:15:03 +0000 Subject: [PATCH 027/104] Remove impossible `UnsupportedEncodingException` from `Id3Decoder` The list of charsets is already hard-coded, and using `Charset` types ensures they will all be present at run-time, hence we will never encounter an 'unsupported' charset. PiperOrigin-RevId: 491324466 (cherry picked from commit 043546a03475356764cf8b754bd0fff87a0c6e1a) --- .../exoplayer2/metadata/id3/Id3Decoder.java | 104 ++++++++---------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index ccf9f2f715..0ee41664e4 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -25,9 +25,10 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; +import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -434,30 +435,25 @@ public final class Id3Decoder extends SimpleMetadataDecoder { + frameSize); } return frame; - } catch (UnsupportedEncodingException e) { - Log.w(TAG, "Unsupported character encoding"); - return null; } finally { id3Data.setPosition(nextFramePosition); } } @Nullable - private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { + private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 1) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfTerminator(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); + String description = new String(data, 0, descriptionEndIndex, getCharset(encoding)); ImmutableList values = decodeTextInformationFrameValues( @@ -467,7 +463,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { @Nullable private static TextInformationFrame decodeTextInformationFrame( - ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException { + ParsableByteArray id3Data, int frameSize, String id) { if (frameSize < 1) { // Frame is malformed. return null; @@ -483,17 +479,17 @@ public final class Id3Decoder extends SimpleMetadataDecoder { } private static ImmutableList decodeTextInformationFrameValues( - byte[] data, final int encoding, final int index) throws UnsupportedEncodingException { + byte[] data, final int encoding, final int index) { if (index >= data.length) { return ImmutableList.of(""); } ImmutableList.Builder values = ImmutableList.builder(); - String charset = getCharsetName(encoding); int valueStartIndex = index; int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { - String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); + String value = + new String(data, valueStartIndex, valueEndIndex - valueStartIndex, getCharset(encoding)); values.add(value); valueStartIndex = valueEndIndex + delimiterLength(encoding); @@ -505,47 +501,44 @@ public final class Id3Decoder extends SimpleMetadataDecoder { } @Nullable - private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { + private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 1) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfTerminator(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); + String description = new String(data, 0, descriptionEndIndex, getCharset(encoding)); int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); int urlEndIndex = indexOfZeroByte(data, urlStartIndex); - String url = decodeStringIfValid(data, urlStartIndex, urlEndIndex, "ISO-8859-1"); + String url = decodeStringIfValid(data, urlStartIndex, urlEndIndex, Charsets.ISO_8859_1); return new UrlLinkFrame("WXXX", description, url); } private static UrlLinkFrame decodeUrlLinkFrame( - ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException { + ParsableByteArray id3Data, int frameSize, String id) { byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); int urlEndIndex = indexOfZeroByte(data, 0); - String url = new String(data, 0, urlEndIndex, "ISO-8859-1"); + String url = new String(data, 0, urlEndIndex, Charsets.ISO_8859_1); return new UrlLinkFrame(id, null, url); } - private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { + private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) { byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); int ownerEndIndex = indexOfZeroByte(data, 0); - String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); + String owner = new String(data, 0, ownerEndIndex, Charsets.ISO_8859_1); int privateDataStartIndex = ownerEndIndex + 1; byte[] privateData = copyOfRangeIfValid(data, privateDataStartIndex, data.length); @@ -553,16 +546,15 @@ public final class Id3Decoder extends SimpleMetadataDecoder { return new PrivFrame(owner, privateData); } - private static GeobFrame decodeGeobFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { + private static GeobFrame decodeGeobFrame(ParsableByteArray id3Data, int frameSize) { int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); + Charset charset = getCharset(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int mimeTypeEndIndex = indexOfZeroByte(data, 0); - String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); + String mimeType = new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1); int filenameStartIndex = mimeTypeEndIndex + 1; int filenameEndIndex = indexOfTerminator(data, filenameStartIndex, encoding); @@ -580,10 +572,9 @@ public final class Id3Decoder extends SimpleMetadataDecoder { } private static ApicFrame decodeApicFrame( - ParsableByteArray id3Data, int frameSize, int majorVersion) - throws UnsupportedEncodingException { + ParsableByteArray id3Data, int frameSize, int majorVersion) { int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); + Charset charset = getCharset(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); @@ -592,13 +583,13 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int mimeTypeEndIndex; if (majorVersion == 2) { mimeTypeEndIndex = 2; - mimeType = "image/" + Ascii.toLowerCase(new String(data, 0, 3, "ISO-8859-1")); + mimeType = "image/" + Ascii.toLowerCase(new String(data, 0, 3, Charsets.ISO_8859_1)); if ("image/jpg".equals(mimeType)) { mimeType = "image/jpeg"; } } else { mimeTypeEndIndex = indexOfZeroByte(data, 0); - mimeType = Ascii.toLowerCase(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1")); + mimeType = Ascii.toLowerCase(new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1)); if (mimeType.indexOf('/') == -1) { mimeType = "image/" + mimeType; } @@ -619,15 +610,14 @@ public final class Id3Decoder extends SimpleMetadataDecoder { } @Nullable - private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { + private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 4) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); + Charset charset = getCharset(encoding); byte[] data = new byte[3]; id3Data.readBytes(data, 0, 3); @@ -652,13 +642,15 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, - @Nullable FramePredicate framePredicate) - throws UnsupportedEncodingException { + @Nullable FramePredicate framePredicate) { int framePosition = id3Data.getPosition(); int chapterIdEndIndex = indexOfZeroByte(id3Data.getData(), framePosition); String chapterId = new String( - id3Data.getData(), framePosition, chapterIdEndIndex - framePosition, "ISO-8859-1"); + id3Data.getData(), + framePosition, + chapterIdEndIndex - framePosition, + Charsets.ISO_8859_1); id3Data.setPosition(chapterIdEndIndex + 1); int startTime = id3Data.readInt(); @@ -693,13 +685,15 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, - @Nullable FramePredicate framePredicate) - throws UnsupportedEncodingException { + @Nullable FramePredicate framePredicate) { int framePosition = id3Data.getPosition(); int elementIdEndIndex = indexOfZeroByte(id3Data.getData(), framePosition); String elementId = new String( - id3Data.getData(), framePosition, elementIdEndIndex - framePosition, "ISO-8859-1"); + id3Data.getData(), + framePosition, + elementIdEndIndex - framePosition, + Charsets.ISO_8859_1); id3Data.setPosition(elementIdEndIndex + 1); int ctocFlags = id3Data.readUnsignedByte(); @@ -711,7 +705,8 @@ public final class Id3Decoder extends SimpleMetadataDecoder { for (int i = 0; i < childCount; i++) { int startIndex = id3Data.getPosition(); int endIndex = indexOfZeroByte(id3Data.getData(), startIndex); - children[i] = new String(id3Data.getData(), startIndex, endIndex - startIndex, "ISO-8859-1"); + children[i] = + new String(id3Data.getData(), startIndex, endIndex - startIndex, Charsets.ISO_8859_1); id3Data.setPosition(endIndex + 1); } @@ -790,23 +785,18 @@ public final class Id3Decoder extends SimpleMetadataDecoder { return length; } - /** - * Maps encoding byte from ID3v2 frame to a Charset. - * - * @param encodingByte The value of encoding byte from ID3v2 frame. - * @return Charset name. - */ - private static String getCharsetName(int encodingByte) { + /** Maps encoding byte from ID3v2 frame to a {@link Charset}. */ + private static Charset getCharset(int encodingByte) { switch (encodingByte) { case ID3_TEXT_ENCODING_UTF_16: - return "UTF-16"; + return Charsets.UTF_16; case ID3_TEXT_ENCODING_UTF_16BE: - return "UTF-16BE"; + return Charsets.UTF_16BE; case ID3_TEXT_ENCODING_UTF_8: - return "UTF-8"; + return Charsets.UTF_8; case ID3_TEXT_ENCODING_ISO_8859_1: default: - return "ISO-8859-1"; + return Charsets.ISO_8859_1; } } @@ -869,21 +859,19 @@ public final class Id3Decoder extends SimpleMetadataDecoder { /** * Returns a string obtained by decoding the specified range of {@code data} using the specified - * {@code charsetName}. An empty string is returned if the range is invalid. + * {@code charset}. An empty string is returned if the range is invalid. * * @param data The array from which to decode the string. * @param from The start of the range. * @param to The end of the range (exclusive). - * @param charsetName The name of the Charset to use. + * @param charset The {@link Charset} to use. * @return The decoded string, or an empty string if the range is invalid. - * @throws UnsupportedEncodingException If the Charset is not supported. */ - private static String decodeStringIfValid(byte[] data, int from, int to, String charsetName) - throws UnsupportedEncodingException { + private static String decodeStringIfValid(byte[] data, int from, int to, Charset charset) { if (to <= from || to > data.length) { return ""; } - return new String(data, from, to - from, charsetName); + return new String(data, from, to - from, charset); } private static final class Id3Header { From 44d12a5070d3d59ee35d76b8d7f1bf79fea5d6d3 Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 29 Nov 2022 19:23:32 +0000 Subject: [PATCH 028/104] Merge pull request #10776 from dongvanhung:feature/add_support_clear_download_manager_helpers PiperOrigin-RevId: 491336828 (cherry picked from commit 3a7f940f41e278cde88c29201191229ed7d39e49) --- .../android/exoplayer2/offline/DownloadService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java index 8170c49743..0bdde5e9b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java @@ -573,6 +573,17 @@ public abstract class DownloadService extends Service { Util.startForegroundService(context, intent); } + /** + * Clear all {@linkplain DownloadManagerHelper download manager helpers} before restarting the + * service. + * + *

    Calling this method is normally only required if an app supports downloading content for + * multiple users for which different download directories should be used. + */ + public static void clearDownloadManagerHelpers() { + downloadManagerHelpers.clear(); + } + @Override public void onCreate() { if (channelId != null) { From 69ded0f28c552b2a842be9c1149cd00de21902df Mon Sep 17 00:00:00 2001 From: bachinger Date: Tue, 29 Nov 2022 10:59:22 +0000 Subject: [PATCH 029/104] Bump cast sdk version and remove workaround for live duration The fix for b/171657375 (internal) has been shipped with 21.1.0 already (see https://developers.google.com/cast/docs/release-notes#august-8,-2022). PiperOrigin-RevId: 491583727 (cherry picked from commit 04f031d16805a6d6786d135f77d0f6e79c77c8f7) --- extensions/cast/build.gradle | 2 +- .../com/google/android/exoplayer2/ext/cast/CastUtils.java | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index ba333edbe2..a7489a9eeb 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -14,7 +14,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" dependencies { - api 'com.google.android.gms:play-services-cast-framework:21.0.1' + api 'com.google.android.gms:play-services-cast-framework:21.2.0' implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation project(modulePrefix + 'library-common') compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java index 5b96bc5846..af6cd8b21c 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java @@ -26,10 +26,6 @@ import com.google.android.gms.cast.MediaTrack; /** Utility methods for Cast integration. */ /* package */ final class CastUtils { - /** The duration returned by {@link MediaInfo#getStreamDuration()} for live streams. */ - // TODO: Remove once [Internal ref: b/171657375] is fixed. - private static final long LIVE_STREAM_DURATION = -1000; - /** * Returns the duration in microseconds advertised by a media info, or {@link C#TIME_UNSET} if * unknown or not applicable. @@ -42,9 +38,7 @@ import com.google.android.gms.cast.MediaTrack; return C.TIME_UNSET; } long durationMs = mediaInfo.getStreamDuration(); - return durationMs != MediaInfo.UNKNOWN_DURATION && durationMs != LIVE_STREAM_DURATION - ? Util.msToUs(durationMs) - : C.TIME_UNSET; + return durationMs != MediaInfo.UNKNOWN_DURATION ? Util.msToUs(durationMs) : C.TIME_UNSET; } /** From b6477ddddd0cadffd674cdb213b49755ae3a3826 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 29 Nov 2022 14:01:35 +0000 Subject: [PATCH 030/104] Add configuration to support OPUS offload To support OPUS offload, we need to provide a few configuration values that are currently not set due to the lack of devices supporting OPUS offload. PiperOrigin-RevId: 491613716 (cherry picked from commit 4cf877b81428021c4eb2dfa1a743178f280aceb5) --- .../java/com/google/android/exoplayer2/C.java | 9 +- .../android/exoplayer2/util/MimeTypes.java | 2 + .../exoplayer2/audio/DefaultAudioSink.java | 2 + .../DefaultAudioTrackBufferSizeProvider.java | 2 + .../android/exoplayer2/audio/OpusUtil.java | 59 ++ .../exoplayer2/extractor/ogg/OpusReader.java | 38 +- .../exoplayer2/audio/OpusUtilTest.java | 575 +++++++++++++++++- 7 files changed, 646 insertions(+), 41 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index c2ce271b7a..30e645cca7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -194,7 +194,7 @@ public final class C { * #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, * {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link * #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS}, - * {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}. + * {@link #ENCODING_DTS_HD}, {@link #ENCODING_DOLBY_TRUEHD} or {@link #ENCODING_OPUS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -221,7 +221,8 @@ public final class C { ENCODING_AC4, ENCODING_DTS, ENCODING_DTS_HD, - ENCODING_DOLBY_TRUEHD + ENCODING_DOLBY_TRUEHD, + ENCODING_OPUS, }) public @interface Encoding {} @@ -321,6 +322,10 @@ public final class C { * @see AudioFormat#ENCODING_DOLBY_TRUEHD */ public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD; + /** + * @see AudioFormat#ENCODING_OPUS + */ + public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS; /** Represents the behavior affecting whether spatialization will be used. */ @Documented diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 479b213395..fa3530c316 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -567,6 +567,8 @@ public final class MimeTypes { return C.ENCODING_DTS_HD; case MimeTypes.AUDIO_TRUEHD: return C.ENCODING_DOLBY_TRUEHD; + case MimeTypes.AUDIO_OPUS: + return C.ENCODING_OPUS; default: return C.ENCODING_INVALID; } 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 bd9ee4d568..919880b34d 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 @@ -1778,6 +1778,8 @@ public final class DefaultAudioSink implements AudioSink { ? 0 : (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset) * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT); + case C.ENCODING_OPUS: + return OpusUtil.parsePacketAudioSampleCount(buffer); case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT_BIG_ENDIAN: case C.ENCODING_PCM_24BIT: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java index 59faf13f87..c9cc38fd53 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java @@ -248,6 +248,8 @@ public class DefaultAudioTrackBufferSizeProvider return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND; case C.ENCODING_DOLBY_TRUEHD: return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND; + case C.ENCODING_OPUS: + return OpusUtil.MAX_BYTES_PER_SECOND; case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT_BIG_ENDIAN: case C.ENCODING_PCM_24BIT: diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java index 70b1e96a8c..5a9031991a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java @@ -27,6 +27,9 @@ public class OpusUtil { /** Opus streams are always 48000 Hz. */ public static final int SAMPLE_RATE = 48_000; + /** Maximum achievable Opus bitrate. */ + public static final int MAX_BYTES_PER_SECOND = 510 * 1000 / 8; // See RFC 6716. Section 2.1.1 + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; private static final int FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT = 3; @@ -61,6 +64,62 @@ public class OpusUtil { return initializationData; } + /** + * Returns the number of audio samples in the given audio packet. + * + *

    The buffer's position is not modified. + * + * @param buffer The audio packet. + * @return Returns the number of audio samples in the packet. + */ + public static int parsePacketAudioSampleCount(ByteBuffer buffer) { + long packetDurationUs = + getPacketDurationUs(buffer.get(0), buffer.limit() > 1 ? buffer.get(1) : 0); + return (int) (packetDurationUs * SAMPLE_RATE / C.MICROS_PER_SECOND); + } + + /** + * Returns the duration of the given audio packet. + * + * @param buffer The audio packet. + * @return Returns the duration of the given audio packet, in microseconds. + */ + public static long getPacketDurationUs(byte[] buffer) { + return getPacketDurationUs(buffer[0], buffer.length > 1 ? buffer[1] : 0); + } + + private static long getPacketDurationUs(byte packetByte0, byte packetByte1) { + // See RFC6716, Sections 3.1 and 3.2. + int toc = packetByte0 & 0xFF; + int frames; + switch (toc & 0x3) { + case 0: + frames = 1; + break; + case 1: + case 2: + frames = 2; + break; + default: + frames = packetByte1 & 0x3F; + break; + } + + int config = toc >> 3; + int length = config & 0x3; + int frameDurationUs; + if (config >= 16) { + frameDurationUs = 2500 << length; + } else if (config >= 12) { + frameDurationUs = 10000 << (length & 0x1); + } else if (length == 3) { + frameDurationUs = 60000; + } else { + frameDurationUs = 10000 << length; + } + return (long) frames * frameDurationUs; + } + private static int getPreSkipSamples(byte[] header) { return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF); } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java index 531774f93c..6e7051aa14 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java @@ -54,7 +54,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; @Override protected long preparePayload(ParsableByteArray packet) { - return convertTimeToGranule(getPacketDurationUs(packet.getData())); + return convertTimeToGranule(OpusUtil.getPacketDurationUs(packet.getData())); } @Override @@ -121,42 +121,6 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; } } - /** - * Returns the duration of the given audio packet. - * - * @param packet Contains audio data. - * @return Returns the duration of the given audio packet. - */ - private long getPacketDurationUs(byte[] packet) { - int toc = packet[0] & 0xFF; - int frames; - switch (toc & 0x3) { - case 0: - frames = 1; - break; - case 1: - case 2: - frames = 2; - break; - default: - frames = packet[1] & 0x3F; - break; - } - - int config = toc >> 3; - int length = config & 0x3; - if (config >= 16) { - length = 2500 << length; - } else if (config >= 12) { - length = 10000 << (length & 0x1); - } else if (length == 3) { - length = 60000; - } else { - length = 10000 << length; - } - return (long) frames * length; - } - /** * Returns true if the given {@link ParsableByteArray} starts with {@code expectedPrefix}. Does * not change the {@link ParsableByteArray#getPosition() position} of {@code packet}. diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java index d3026359c2..f7eb4e7b18 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.audio; +import static com.google.android.exoplayer2.util.Util.getBytesFromHexString; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -41,8 +42,9 @@ public final class OpusUtilTest { buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES)); @Test - public void buildInitializationData() { + public void buildInitializationData_returnsExpectedHeaderWithPreSkipAndPreRoll() { List initializationData = OpusUtil.buildInitializationData(HEADER); + assertThat(initializationData).hasSize(3); assertThat(initializationData.get(0)).isEqualTo(HEADER); assertThat(initializationData.get(1)).isEqualTo(HEADER_PRE_SKIP_BYTES); @@ -50,11 +52,576 @@ public final class OpusUtilTest { } @Test - public void getChannelCount() { + public void getChannelCount_returnsChannelCount() { int channelCount = OpusUtil.getChannelCount(HEADER); + assertThat(channelCount).isEqualTo(2); } + @Test + public void getPacketDurationUs_code0_returnsExpectedDuration() { + long config0DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("04")); + long config1DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("0C")); + long config2DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("14")); + long config3DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("1C")); + long config4DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("24")); + long config5DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("2C")); + long config6DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("34")); + long config7DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("3C")); + long config8DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("44")); + long config9DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("4C")); + long config10DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("54")); + long config11DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("5C")); + long config12DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("64")); + long config13DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("6C")); + long config14DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("74")); + long config15DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("7C")); + long config16DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("84")); + long config17DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("8C")); + long config18DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("94")); + long config19DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("9C")); + long config20DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("A4")); + long config21DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("AC")); + long config22DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("B4")); + long config23DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("BC")); + long config24DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("C4")); + long config25DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("CC")); + long config26DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("D4")); + long config27DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("DC")); + long config28DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("E4")); + long config29DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("EC")); + long config30DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("F4")); + long config31DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("FC")); + + assertThat(config0DurationUs).isEqualTo(10_000); + assertThat(config1DurationUs).isEqualTo(20_000); + assertThat(config2DurationUs).isEqualTo(40_000); + assertThat(config3DurationUs).isEqualTo(60_000); + assertThat(config4DurationUs).isEqualTo(10_000); + assertThat(config5DurationUs).isEqualTo(20_000); + assertThat(config6DurationUs).isEqualTo(40_000); + assertThat(config7DurationUs).isEqualTo(60_000); + assertThat(config8DurationUs).isEqualTo(10_000); + assertThat(config9DurationUs).isEqualTo(20_000); + assertThat(config10DurationUs).isEqualTo(40_000); + assertThat(config11DurationUs).isEqualTo(60_000); + assertThat(config12DurationUs).isEqualTo(10_000); + assertThat(config13DurationUs).isEqualTo(20_000); + assertThat(config14DurationUs).isEqualTo(10_000); + assertThat(config15DurationUs).isEqualTo(20_000); + assertThat(config16DurationUs).isEqualTo(2_500); + assertThat(config17DurationUs).isEqualTo(5_000); + assertThat(config18DurationUs).isEqualTo(10_000); + assertThat(config19DurationUs).isEqualTo(20_000); + assertThat(config20DurationUs).isEqualTo(2_500); + assertThat(config21DurationUs).isEqualTo(5_000); + assertThat(config22DurationUs).isEqualTo(10_000); + assertThat(config23DurationUs).isEqualTo(20_000); + assertThat(config24DurationUs).isEqualTo(2_500); + assertThat(config25DurationUs).isEqualTo(5_000); + assertThat(config26DurationUs).isEqualTo(10_000); + assertThat(config27DurationUs).isEqualTo(20_000); + assertThat(config28DurationUs).isEqualTo(2_500); + assertThat(config29DurationUs).isEqualTo(5_000); + assertThat(config30DurationUs).isEqualTo(10_000); + assertThat(config31DurationUs).isEqualTo(20_000); + } + + @Test + public void getPacketDurationUs_code1_returnsExpectedDuration() { + long config0DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("05")); + long config1DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("0D")); + long config2DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("15")); + long config3DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("1D")); + long config4DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("25")); + long config5DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("2D")); + long config6DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("35")); + long config7DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("3D")); + long config8DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("45")); + long config9DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("4D")); + long config10DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("55")); + long config11DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("5D")); + long config12DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("65")); + long config13DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("6D")); + long config14DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("75")); + long config15DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("7D")); + long config16DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("85")); + long config17DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("8D")); + long config18DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("95")); + long config19DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("9D")); + long config20DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("A5")); + long config21DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("AD")); + long config22DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("B5")); + long config23DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("BD")); + long config24DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("C5")); + long config25DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("CD")); + long config26DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("D5")); + long config27DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("DD")); + long config28DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("E5")); + long config29DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("ED")); + long config30DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("F5")); + long config31DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("FD")); + + assertThat(config0DurationUs).isEqualTo(20_000); + assertThat(config1DurationUs).isEqualTo(40_000); + assertThat(config2DurationUs).isEqualTo(80_000); + assertThat(config3DurationUs).isEqualTo(120_000); + assertThat(config4DurationUs).isEqualTo(20_000); + assertThat(config5DurationUs).isEqualTo(40_000); + assertThat(config6DurationUs).isEqualTo(80_000); + assertThat(config7DurationUs).isEqualTo(120_000); + assertThat(config8DurationUs).isEqualTo(20_000); + assertThat(config9DurationUs).isEqualTo(40_000); + assertThat(config10DurationUs).isEqualTo(80_000); + assertThat(config11DurationUs).isEqualTo(120_000); + assertThat(config12DurationUs).isEqualTo(20_000); + assertThat(config13DurationUs).isEqualTo(40_000); + assertThat(config14DurationUs).isEqualTo(20_000); + assertThat(config15DurationUs).isEqualTo(40_000); + assertThat(config16DurationUs).isEqualTo(5_000); + assertThat(config17DurationUs).isEqualTo(10_000); + assertThat(config18DurationUs).isEqualTo(20_000); + assertThat(config19DurationUs).isEqualTo(40_000); + assertThat(config20DurationUs).isEqualTo(5_000); + assertThat(config21DurationUs).isEqualTo(10_000); + assertThat(config22DurationUs).isEqualTo(20_000); + assertThat(config23DurationUs).isEqualTo(40_000); + assertThat(config24DurationUs).isEqualTo(5_000); + assertThat(config25DurationUs).isEqualTo(10_000); + assertThat(config26DurationUs).isEqualTo(20_000); + assertThat(config27DurationUs).isEqualTo(40_000); + assertThat(config28DurationUs).isEqualTo(5_000); + assertThat(config29DurationUs).isEqualTo(10_000); + assertThat(config30DurationUs).isEqualTo(20_000); + assertThat(config31DurationUs).isEqualTo(40_000); + } + + @Test + public void getPacketDurationUs_code2_returnsExpectedDuration() { + long config0DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("06")); + long config1DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("0E")); + long config2DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("16")); + long config3DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("1E")); + long config4DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("26")); + long config5DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("2E")); + long config6DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("36")); + long config7DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("3E")); + long config8DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("46")); + long config9DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("4E")); + long config10DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("56")); + long config11DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("5E")); + long config12DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("66")); + long config13DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("6E")); + long config14DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("76")); + long config15DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("7E")); + long config16DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("86")); + long config17DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("8E")); + long config18DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("96")); + long config19DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("9E")); + long config20DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("A6")); + long config21DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("AE")); + long config22DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("B6")); + long config23DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("BE")); + long config24DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("C6")); + long config25DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("CE")); + long config26DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("D6")); + long config27DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("DE")); + long config28DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("E6")); + long config29DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("EE")); + long config30DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("F6")); + long config31DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("FE")); + + assertThat(config0DurationUs).isEqualTo(20_000); + assertThat(config1DurationUs).isEqualTo(40_000); + assertThat(config2DurationUs).isEqualTo(80_000); + assertThat(config3DurationUs).isEqualTo(120_000); + assertThat(config4DurationUs).isEqualTo(20_000); + assertThat(config5DurationUs).isEqualTo(40_000); + assertThat(config6DurationUs).isEqualTo(80_000); + assertThat(config7DurationUs).isEqualTo(120_000); + assertThat(config8DurationUs).isEqualTo(20_000); + assertThat(config9DurationUs).isEqualTo(40_000); + assertThat(config10DurationUs).isEqualTo(80_000); + assertThat(config11DurationUs).isEqualTo(120_000); + assertThat(config12DurationUs).isEqualTo(20_000); + assertThat(config13DurationUs).isEqualTo(40_000); + assertThat(config14DurationUs).isEqualTo(20_000); + assertThat(config15DurationUs).isEqualTo(40_000); + assertThat(config16DurationUs).isEqualTo(5_000); + assertThat(config17DurationUs).isEqualTo(10_000); + assertThat(config18DurationUs).isEqualTo(20_000); + assertThat(config19DurationUs).isEqualTo(40_000); + assertThat(config20DurationUs).isEqualTo(5_000); + assertThat(config21DurationUs).isEqualTo(10_000); + assertThat(config22DurationUs).isEqualTo(20_000); + assertThat(config23DurationUs).isEqualTo(40_000); + assertThat(config24DurationUs).isEqualTo(5_000); + assertThat(config25DurationUs).isEqualTo(10_000); + assertThat(config26DurationUs).isEqualTo(20_000); + assertThat(config27DurationUs).isEqualTo(40_000); + assertThat(config28DurationUs).isEqualTo(5_000); + assertThat(config29DurationUs).isEqualTo(10_000); + assertThat(config30DurationUs).isEqualTo(20_000); + assertThat(config31DurationUs).isEqualTo(40_000); + } + + @Test + public void getPacketDurationUs_code3_returnsExpectedDuration() { + // max possible frame count to reach 120ms duration + long config0DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("078C")); + long config1DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("0F86")); + long config2DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("1783")); + long config3DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("1F82")); + // frame count of 2 + long config4DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("2782")); + long config5DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("2F82")); + long config6DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("3782")); + long config7DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("3F82")); + long config8DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("4782")); + long config9DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("4F82")); + long config10DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("5782")); + long config11DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("5F82")); + // max possible frame count to reach 120ms duration + long config12DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("678C")); + long config13DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("6F86")); + // frame count of 2 + long config14DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("7782")); + long config15DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("7F82")); + // max possible frame count to reach 120ms duration + long config16DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("87B0")); + long config17DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("8F98")); + long config18DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("978C")); + long config19DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("9F86")); + // frame count of 2 + long config20DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("A782")); + long config21DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("AF82")); + long config22DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("B782")); + long config23DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("BF82")); + long config24DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("C782")); + long config25DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("CF82")); + long config26DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("D782")); + long config27DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("DF82")); + long config28DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("E782")); + long config29DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("EF82")); + long config30DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("F782")); + long config31DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("FF82")); + + assertThat(config0DurationUs).isEqualTo(120_000); + assertThat(config1DurationUs).isEqualTo(120_000); + assertThat(config2DurationUs).isEqualTo(120_000); + assertThat(config3DurationUs).isEqualTo(120_000); + assertThat(config4DurationUs).isEqualTo(20_000); + assertThat(config5DurationUs).isEqualTo(40_000); + assertThat(config6DurationUs).isEqualTo(80_000); + assertThat(config7DurationUs).isEqualTo(120_000); + assertThat(config8DurationUs).isEqualTo(20_000); + assertThat(config9DurationUs).isEqualTo(40_000); + assertThat(config10DurationUs).isEqualTo(80_000); + assertThat(config11DurationUs).isEqualTo(120_000); + assertThat(config12DurationUs).isEqualTo(120_000); + assertThat(config13DurationUs).isEqualTo(120_000); + assertThat(config14DurationUs).isEqualTo(20_000); + assertThat(config15DurationUs).isEqualTo(40_000); + assertThat(config16DurationUs).isEqualTo(120_000); + assertThat(config17DurationUs).isEqualTo(120_000); + assertThat(config18DurationUs).isEqualTo(120_000); + assertThat(config19DurationUs).isEqualTo(120_000); + assertThat(config20DurationUs).isEqualTo(5_000); + assertThat(config21DurationUs).isEqualTo(10_000); + assertThat(config22DurationUs).isEqualTo(20_000); + assertThat(config23DurationUs).isEqualTo(40_000); + assertThat(config24DurationUs).isEqualTo(5_000); + assertThat(config25DurationUs).isEqualTo(10_000); + assertThat(config26DurationUs).isEqualTo(20_000); + assertThat(config27DurationUs).isEqualTo(40_000); + assertThat(config28DurationUs).isEqualTo(5_000); + assertThat(config29DurationUs).isEqualTo(10_000); + assertThat(config30DurationUs).isEqualTo(20_000); + assertThat(config31DurationUs).isEqualTo(40_000); + } + + @Test + public void getPacketAudioSampleCount_code0_returnsExpectedDuration() { + int config0SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("04")); + int config1SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("0C")); + int config2SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("14")); + int config3SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("1C")); + int config4SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("24")); + int config5SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("2C")); + int config6SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("34")); + int config7SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("3C")); + int config8SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("44")); + int config9SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("4C")); + int config10SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("54")); + int config11SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("5C")); + int config12SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("64")); + int config13SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("6C")); + int config14SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("74")); + int config15SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("7C")); + int config16SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("84")); + int config17SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("8C")); + int config18SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("94")); + int config19SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("9C")); + int config20SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("A4")); + int config21SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("AC")); + int config22SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("B4")); + int config23SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("BC")); + int config24SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("C4")); + int config25SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("CC")); + int config26SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("D4")); + int config27SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("DC")); + int config28SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("E4")); + int config29SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("EC")); + int config30SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("F4")); + int config31SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("FC")); + + assertThat(config0SampleCount).isEqualTo(480); + assertThat(config1SampleCount).isEqualTo(960); + assertThat(config2SampleCount).isEqualTo(1920); + assertThat(config3SampleCount).isEqualTo(2880); + assertThat(config4SampleCount).isEqualTo(480); + assertThat(config5SampleCount).isEqualTo(960); + assertThat(config6SampleCount).isEqualTo(1920); + assertThat(config7SampleCount).isEqualTo(2880); + assertThat(config8SampleCount).isEqualTo(480); + assertThat(config9SampleCount).isEqualTo(960); + assertThat(config10SampleCount).isEqualTo(1920); + assertThat(config11SampleCount).isEqualTo(2880); + assertThat(config12SampleCount).isEqualTo(480); + assertThat(config13SampleCount).isEqualTo(960); + assertThat(config14SampleCount).isEqualTo(480); + assertThat(config15SampleCount).isEqualTo(960); + assertThat(config16SampleCount).isEqualTo(120); + assertThat(config17SampleCount).isEqualTo(240); + assertThat(config18SampleCount).isEqualTo(480); + assertThat(config19SampleCount).isEqualTo(960); + assertThat(config20SampleCount).isEqualTo(120); + assertThat(config21SampleCount).isEqualTo(240); + assertThat(config22SampleCount).isEqualTo(480); + assertThat(config23SampleCount).isEqualTo(960); + assertThat(config24SampleCount).isEqualTo(120); + assertThat(config25SampleCount).isEqualTo(240); + assertThat(config26SampleCount).isEqualTo(480); + assertThat(config27SampleCount).isEqualTo(960); + assertThat(config28SampleCount).isEqualTo(120); + assertThat(config29SampleCount).isEqualTo(240); + assertThat(config30SampleCount).isEqualTo(480); + assertThat(config31SampleCount).isEqualTo(960); + } + + @Test + public void getPacketAudioSampleCount_code1_returnsExpectedDuration() { + int config0SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("05")); + int config1SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("0D")); + int config2SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("15")); + int config3SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("1D")); + int config4SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("25")); + int config5SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("2D")); + int config6SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("35")); + int config7SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("3D")); + int config8SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("45")); + int config9SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("4D")); + int config10SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("55")); + int config11SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("5D")); + int config12SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("65")); + int config13SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("6D")); + int config14SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("75")); + int config15SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("7D")); + int config16SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("85")); + int config17SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("8D")); + int config18SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("95")); + int config19SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("9D")); + int config20SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("A5")); + int config21SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("AD")); + int config22SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("B5")); + int config23SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("BD")); + int config24SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("C5")); + int config25SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("CD")); + int config26SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("D5")); + int config27SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("DD")); + int config28SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("E5")); + int config29SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("ED")); + int config30SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("F5")); + int config31SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("FD")); + + assertThat(config0SampleCount).isEqualTo(960); + assertThat(config1SampleCount).isEqualTo(1920); + assertThat(config2SampleCount).isEqualTo(3840); + assertThat(config3SampleCount).isEqualTo(5760); + assertThat(config4SampleCount).isEqualTo(960); + assertThat(config5SampleCount).isEqualTo(1920); + assertThat(config6SampleCount).isEqualTo(3840); + assertThat(config7SampleCount).isEqualTo(5760); + assertThat(config8SampleCount).isEqualTo(960); + assertThat(config9SampleCount).isEqualTo(1920); + assertThat(config10SampleCount).isEqualTo(3840); + assertThat(config11SampleCount).isEqualTo(5760); + assertThat(config12SampleCount).isEqualTo(960); + assertThat(config13SampleCount).isEqualTo(1920); + assertThat(config14SampleCount).isEqualTo(960); + assertThat(config15SampleCount).isEqualTo(1920); + assertThat(config16SampleCount).isEqualTo(240); + assertThat(config17SampleCount).isEqualTo(480); + assertThat(config18SampleCount).isEqualTo(960); + assertThat(config19SampleCount).isEqualTo(1920); + assertThat(config20SampleCount).isEqualTo(240); + assertThat(config21SampleCount).isEqualTo(480); + assertThat(config22SampleCount).isEqualTo(960); + assertThat(config23SampleCount).isEqualTo(1920); + assertThat(config24SampleCount).isEqualTo(240); + assertThat(config25SampleCount).isEqualTo(480); + assertThat(config26SampleCount).isEqualTo(960); + assertThat(config27SampleCount).isEqualTo(1920); + assertThat(config28SampleCount).isEqualTo(240); + assertThat(config29SampleCount).isEqualTo(480); + assertThat(config30SampleCount).isEqualTo(960); + assertThat(config31SampleCount).isEqualTo(1920); + } + + @Test + public void getPacketAudioSampleCount_code2_returnsExpectedDuration() { + int config0SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("06")); + int config1SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("0E")); + int config2SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("16")); + int config3SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("1E")); + int config4SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("26")); + int config5SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("2E")); + int config6SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("36")); + int config7SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("3E")); + int config8SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("46")); + int config9SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("4E")); + int config10SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("56")); + int config11SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("5E")); + int config12SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("66")); + int config13SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("6E")); + int config14SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("76")); + int config15SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("7E")); + int config16SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("86")); + int config17SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("8E")); + int config18SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("96")); + int config19SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("9E")); + int config20SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("A6")); + int config21SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("AE")); + int config22SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("B6")); + int config23SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("BE")); + int config24SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("C6")); + int config25SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("CE")); + int config26SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("D6")); + int config27SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("DE")); + int config28SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("E6")); + int config29SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("EE")); + int config30SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("F6")); + int config31SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("FE")); + + assertThat(config0SampleCount).isEqualTo(960); + assertThat(config1SampleCount).isEqualTo(1920); + assertThat(config2SampleCount).isEqualTo(3840); + assertThat(config3SampleCount).isEqualTo(5760); + assertThat(config4SampleCount).isEqualTo(960); + assertThat(config5SampleCount).isEqualTo(1920); + assertThat(config6SampleCount).isEqualTo(3840); + assertThat(config7SampleCount).isEqualTo(5760); + assertThat(config8SampleCount).isEqualTo(960); + assertThat(config9SampleCount).isEqualTo(1920); + assertThat(config10SampleCount).isEqualTo(3840); + assertThat(config11SampleCount).isEqualTo(5760); + assertThat(config12SampleCount).isEqualTo(960); + assertThat(config13SampleCount).isEqualTo(1920); + assertThat(config14SampleCount).isEqualTo(960); + assertThat(config15SampleCount).isEqualTo(1920); + assertThat(config16SampleCount).isEqualTo(240); + assertThat(config17SampleCount).isEqualTo(480); + assertThat(config18SampleCount).isEqualTo(960); + assertThat(config19SampleCount).isEqualTo(1920); + assertThat(config20SampleCount).isEqualTo(240); + assertThat(config21SampleCount).isEqualTo(480); + assertThat(config22SampleCount).isEqualTo(960); + assertThat(config23SampleCount).isEqualTo(1920); + assertThat(config24SampleCount).isEqualTo(240); + assertThat(config25SampleCount).isEqualTo(480); + assertThat(config26SampleCount).isEqualTo(960); + assertThat(config27SampleCount).isEqualTo(1920); + assertThat(config28SampleCount).isEqualTo(240); + assertThat(config29SampleCount).isEqualTo(480); + assertThat(config30SampleCount).isEqualTo(960); + assertThat(config31SampleCount).isEqualTo(1920); + } + + @Test + public void getPacketAudioSampleCount_code3_returnsExpectedDuration() { + // max possible frame count to reach 120ms duration + int config0SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("078C")); + int config1SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("0F86")); + int config2SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("1783")); + int config3SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("1F82")); + // frame count of 2 + int config4SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("2782")); + int config5SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("2F82")); + int config6SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("3782")); + int config7SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("3F82")); + int config8SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("4782")); + int config9SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("4F82")); + int config10SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("5782")); + int config11SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("5F82")); + // max possible frame count to reach 120ms duration + int config12SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("678C")); + int config13SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("6F86")); + // frame count of 2 + int config14SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("7782")); + int config15SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("7F82")); + // max possible frame count to reach 120ms duration + int config16SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("87B0")); + int config17SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("8F98")); + int config18SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("978C")); + int config19SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("9F86")); + // frame count of 2 + int config20SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("A782")); + int config21SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("AF82")); + int config22SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("B782")); + int config23SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("BF82")); + int config24SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("C782")); + int config25SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("CF82")); + int config26SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("D782")); + int config27SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("DF82")); + int config28SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("E782")); + int config29SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("EF82")); + int config30SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("F782")); + int config31SampleCount = OpusUtil.parsePacketAudioSampleCount(getByteBuffer("FF82")); + + assertThat(config0SampleCount).isEqualTo(5760); + assertThat(config1SampleCount).isEqualTo(5760); + assertThat(config2SampleCount).isEqualTo(5760); + assertThat(config3SampleCount).isEqualTo(5760); + assertThat(config4SampleCount).isEqualTo(960); + assertThat(config5SampleCount).isEqualTo(1920); + assertThat(config6SampleCount).isEqualTo(3840); + assertThat(config7SampleCount).isEqualTo(5760); + assertThat(config8SampleCount).isEqualTo(960); + assertThat(config9SampleCount).isEqualTo(1920); + assertThat(config10SampleCount).isEqualTo(3840); + assertThat(config11SampleCount).isEqualTo(5760); + assertThat(config12SampleCount).isEqualTo(5760); + assertThat(config13SampleCount).isEqualTo(5760); + assertThat(config14SampleCount).isEqualTo(960); + assertThat(config15SampleCount).isEqualTo(1920); + assertThat(config16SampleCount).isEqualTo(5760); + assertThat(config17SampleCount).isEqualTo(5760); + assertThat(config18SampleCount).isEqualTo(5760); + assertThat(config19SampleCount).isEqualTo(5760); + assertThat(config20SampleCount).isEqualTo(240); + assertThat(config21SampleCount).isEqualTo(480); + assertThat(config22SampleCount).isEqualTo(960); + assertThat(config23SampleCount).isEqualTo(1920); + assertThat(config24SampleCount).isEqualTo(240); + assertThat(config25SampleCount).isEqualTo(480); + assertThat(config26SampleCount).isEqualTo(960); + assertThat(config27SampleCount).isEqualTo(1920); + assertThat(config28SampleCount).isEqualTo(240); + assertThat(config29SampleCount).isEqualTo(480); + assertThat(config30SampleCount).isEqualTo(960); + assertThat(config31SampleCount).isEqualTo(1920); + } + private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / OpusUtil.SAMPLE_RATE; } @@ -62,4 +629,8 @@ public final class OpusUtilTest { private static byte[] buildNativeOrderByteArray(long value) { return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array(); } + + private static ByteBuffer getByteBuffer(String hexString) { + return ByteBuffer.wrap(getBytesFromHexString(hexString)); + } } From d7923785a7aa4c1a81756f469909dfa8f5bbfac9 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 29 Nov 2022 15:18:11 +0000 Subject: [PATCH 031/104] Use audio bitrate to calculate AudioTrack min buffer in passthrough Use the bitrate of the audio format (when available) in DefaultAudioSink.AudioTrackBufferSizeProvider.getBufferSizeInBytes() to calculate accurate buffer sizes for direct (passthrough) playbacks. #minor-release PiperOrigin-RevId: 491628530 (cherry picked from commit e219ac21ae604f182769d69f6f590191a92100d0) --- .../exoplayer2/audio/DefaultAudioSink.java | 4 ++ .../DefaultAudioTrackBufferSizeProvider.java | 23 +++++++--- ...ltAudioTrackBufferSizeProviderAC3Test.java | 21 ++++++++- ...dioTrackBufferSizeProviderEncodedTest.java | 43 +++++++++++++++++++ ...ltAudioTrackBufferSizeProviderPcmTest.java | 9 ++++ 5 files changed, 93 insertions(+), 7 deletions(-) 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 919880b34d..8016384f69 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 @@ -194,6 +194,8 @@ public final class DefaultAudioSink implements AudioSink { * @param pcmFrameSize The size of the PCM frames if the {@code encoding} is PCM, 1 otherwise, * in bytes. * @param sampleRate The sample rate of the format, in Hz. + * @param bitrate The bitrate of the audio stream if the stream is compressed, or {@link + * Format#NO_VALUE} if {@code encoding} is PCM or the bitrate is not known. * @param maxAudioTrackPlaybackSpeed The maximum speed the content will be played using {@link * AudioTrack#setPlaybackParams}. 0.5 is 2x slow motion, 1 is real time, 2 is 2x fast * forward, etc. This will be {@code 1} unless {@link @@ -208,6 +210,7 @@ public final class DefaultAudioSink implements AudioSink { @OutputMode int outputMode, int pcmFrameSize, int sampleRate, + int bitrate, double maxAudioTrackPlaybackSpeed); } @@ -781,6 +784,7 @@ public final class DefaultAudioSink implements AudioSink { outputMode, outputPcmFrameSize != C.LENGTH_UNSET ? outputPcmFrameSize : 1, outputSampleRate, + inputFormat.bitrate, enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED); offloadDisabledUntilNextConfiguration = false; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java index c9cc38fd53..2e774551fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProvider.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_O import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH; import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PCM; import static com.google.android.exoplayer2.util.Util.constrainValue; +import static com.google.common.math.IntMath.divide; import static com.google.common.primitives.Ints.checkedCast; import static java.lang.Math.max; @@ -27,6 +28,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.math.RoundingMode; /** Provide the buffer size to use when creating an {@link AudioTrack}. */ public class DefaultAudioTrackBufferSizeProvider @@ -166,10 +168,11 @@ public class DefaultAudioTrackBufferSizeProvider @OutputMode int outputMode, int pcmFrameSize, int sampleRate, + int bitrate, double maxAudioTrackPlaybackSpeed) { int bufferSize = get1xBufferSizeInBytes( - minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate); + minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate, bitrate); // Maintain the buffer duration by scaling the size accordingly. bufferSize = (int) (bufferSize * maxAudioTrackPlaybackSpeed); // Buffer size must not be lower than the AudioTrack min buffer size for this format. @@ -180,12 +183,17 @@ public class DefaultAudioTrackBufferSizeProvider /** Returns the buffer size for playback at 1x speed. */ protected int get1xBufferSizeInBytes( - int minBufferSizeInBytes, int encoding, int outputMode, int pcmFrameSize, int sampleRate) { + int minBufferSizeInBytes, + int encoding, + int outputMode, + int pcmFrameSize, + int sampleRate, + int bitrate) { switch (outputMode) { case OUTPUT_MODE_PCM: return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize); case OUTPUT_MODE_PASSTHROUGH: - return getPassthroughBufferSizeInBytes(encoding); + return getPassthroughBufferSizeInBytes(encoding, bitrate); case OUTPUT_MODE_OFFLOAD: return getOffloadBufferSizeInBytes(encoding); default: @@ -202,13 +210,16 @@ public class DefaultAudioTrackBufferSizeProvider } /** Returns the buffer size for passthrough playback. */ - protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding) { + protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding, int bitrate) { int bufferSizeUs = passthroughBufferDurationUs; if (encoding == C.ENCODING_AC3) { bufferSizeUs *= ac3BufferMultiplicationFactor; } - int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding); - return checkedCast((long) bufferSizeUs * maxByteRate / C.MICROS_PER_SECOND); + int byteRate = + bitrate != Format.NO_VALUE + ? divide(bitrate, 8, RoundingMode.CEILING) + : getMaximumEncodedRateBytesPerSecond(encoding); + return checkedCast((long) bufferSizeUs * byteRate / C.MICROS_PER_SECOND); } /** Returns the buffer size for offload playback. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java index f282f1684e..d93bcaac26 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderAC3Test.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +35,7 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { @Test public void - getBufferSizeInBytes_passthroughAC3_isPassthroughBufferSizeTimesMultiplicationFactor() { + getBufferSizeInBytes_passthroughAc3AndNoBitrate_assumesMaxByteRateTimesMultiplicationFactor() { int bufferSize = DEFAULT.getBufferSizeInBytes( /* minBufferSizeInBytes= */ 0, @@ -42,6 +43,7 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, /* pcmFrameSize= */ 1, /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -50,6 +52,23 @@ public class DefaultAudioTrackBufferSizeProviderAC3Test { * DEFAULT.ac3BufferMultiplicationFactor); } + @Test + public void + getBufferSizeInBytes_passthroughAC3At256Kbits_isPassthroughBufferSizeTimesMultiplicationFactor() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ C.ENCODING_AC3, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ 256_000, + /* maxAudioTrackPlaybackSpeed= */ 1); + + // Default buffer duration 0.25s => 0.25 * 256000 / 8 = 8000 + assertThat(bufferSize).isEqualTo(8000 * DEFAULT.ac3BufferMultiplicationFactor); + } + private static int durationUsToAc3MaxBytes(long durationUs) { return (int) (durationUs * getMaximumEncodedRateBytesPerSecond(C.ENCODING_AC3) / MICROS_PER_SECOND); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java index 30db87083d..9d3a7e40a8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderEncodedTest.java @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.audio; +import static com.google.android.exoplayer2.C.MICROS_PER_SECOND; import static com.google.android.exoplayer2.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH; +import static com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider.getMaximumEncodedRateBytesPerSecond; import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +46,8 @@ public class DefaultAudioTrackBufferSizeProviderEncodedTest { C.ENCODING_MP3, C.ENCODING_AAC_LC, C.ENCODING_AAC_HE_V1, + C.ENCODING_E_AC3, + C.ENCODING_E_AC3_JOC, C.ENCODING_AC4, C.ENCODING_DTS, C.ENCODING_DOLBY_TRUEHD); @@ -57,8 +62,46 @@ public class DefaultAudioTrackBufferSizeProviderEncodedTest { /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, /* pcmFrameSize= */ 1, /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 0); assertThat(bufferSize).isEqualTo(123456789); } + + @Test + public void + getBufferSizeInBytes_passThroughAndBitrateNotSet_returnsBufferSizeWithAssumedBitrate() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ encoding, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ Format.NO_VALUE, + /* maxAudioTrackPlaybackSpeed= */ 1); + + assertThat(bufferSize) + .isEqualTo(durationUsToMaxBytes(encoding, DEFAULT.passthroughBufferDurationUs)); + } + + @Test + public void getBufferSizeInBytes_passthroughAndBitrateDefined() { + int bufferSize = + DEFAULT.getBufferSizeInBytes( + /* minBufferSizeInBytes= */ 0, + /* encoding= */ encoding, + /* outputMode= */ OUTPUT_MODE_PASSTHROUGH, + /* pcmFrameSize= */ 1, + /* sampleRate= */ 0, + /* bitrate= */ 256_000, + /* maxAudioTrackPlaybackSpeed= */ 1); + + // Default buffer duration is 250ms => 0.25 * 256000 / 8 = 8000 + assertThat(bufferSize).isEqualTo(8000); + } + + private static int durationUsToMaxBytes(@C.Encoding int encoding, long durationUs) { + return (int) (durationUs * getMaximumEncodedRateBytesPerSecond(encoding) / MICROS_PER_SECOND); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java index 06c0257548..293e14cf5f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioTrackBufferSizeProviderPcmTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.ceil; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -89,6 +90,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize).isEqualTo(roundUpToFrame(1234567890)); @@ -103,6 +105,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -121,6 +124,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -139,6 +143,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -157,6 +162,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -175,6 +181,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1); assertThat(bufferSize) @@ -190,6 +197,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 1 / 5F); assertThat(bufferSize) @@ -205,6 +213,7 @@ public class DefaultAudioTrackBufferSizeProviderPcmTest { /* outputMode= */ OUTPUT_MODE_PCM, /* pcmFrameSize= */ getPcmFrameSize(), /* sampleRate= */ sampleRate, + /* bitrate= */ Format.NO_VALUE, /* maxAudioTrackPlaybackSpeed= */ 8F); int expected = roundUpToFrame(durationUsToBytes(DEFAULT.minPcmBufferDurationUs) * 8); From 2921cb76c5ace96e4b2fbb3ea8b7143b519b8773 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 30 Nov 2022 12:48:11 +0000 Subject: [PATCH 032/104] Rename SimpleBasePlayer.PlaylistItem to MediaItemData This better matches the terminology we use elsewhere in the Player interface, where items inside the playlist are referred to as "media item" and only the entire list is called "playlist". PiperOrigin-RevId: 491882849 (cherry picked from commit 6c467590d0fdc27dd5afeefe479c3d4414483de5) --- .../android/exoplayer2/SimpleBasePlayer.java | 312 +++++++++--------- .../exoplayer2/SimpleBasePlayerTest.java | 247 +++++++------- 2 files changed, 281 insertions(+), 278 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index aa904e591c..7a6e231a30 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -125,7 +125,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { private Size surfaceSize; private boolean newlyRenderedFirstFrame; private Metadata timedMetadata; - private ImmutableList playlistItems; + private ImmutableList playlist; private Timeline timeline; private MediaMetadata playlistMetadata; private int currentMediaItemIndex; @@ -171,7 +171,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { surfaceSize = Size.UNKNOWN; newlyRenderedFirstFrame = false; timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET); - playlistItems = ImmutableList.of(); + playlist = ImmutableList.of(); timeline = Timeline.EMPTY; playlistMetadata = MediaMetadata.EMPTY; currentMediaItemIndex = 0; @@ -217,7 +217,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.surfaceSize = state.surfaceSize; this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame; this.timedMetadata = state.timedMetadata; - this.playlistItems = state.playlistItems; + this.playlist = state.playlist; this.timeline = state.timeline; this.playlistMetadata = state.playlistMetadata; this.currentMediaItemIndex = state.currentMediaItemIndex; @@ -568,21 +568,21 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the playlist items. + * Sets the list of {@link MediaItemData media items} in the playlist. * - *

    All playlist items must have unique {@linkplain PlaylistItem.Builder#setUid UIDs}. + *

    All items must have unique {@linkplain MediaItemData.Builder#setUid UIDs}. * - * @param playlistItems The list of playlist items. + * @param playlist The list of {@link MediaItemData media items} in the playlist. * @return This builder. */ @CanIgnoreReturnValue - public Builder setPlaylist(List playlistItems) { + public Builder setPlaylist(List playlist) { HashSet uids = new HashSet<>(); - for (int i = 0; i < playlistItems.size(); i++) { - checkArgument(uids.add(playlistItems.get(i).uid)); + for (int i = 0; i < playlist.size(); i++) { + checkArgument(uids.add(playlist.get(i).uid)); } - this.playlistItems = ImmutableList.copyOf(playlistItems); - this.timeline = new PlaylistTimeline(this.playlistItems); + this.playlist = ImmutableList.copyOf(playlist); + this.timeline = new PlaylistTimeline(this.playlist); return this; } @@ -601,8 +601,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { /** * Sets the current media item index. * - *

    The media item index must be less than the number of {@linkplain #setPlaylist playlist - * items}, if set. + *

    The media item index must be less than the number of {@linkplain #setPlaylist media + * items in the playlist}, if set. * * @param currentMediaItemIndex The current media item index. * @return This builder. @@ -615,15 +615,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { /** * Sets the current period index, or {@link C#INDEX_UNSET} to assume the first period of the - * current playlist item is played. + * current media item is played. * *

    The period index must be less than the total number of {@linkplain - * PlaylistItem.Builder#setPeriods periods} in the playlist, if set, and the period at the - * specified index must be part of the {@linkplain #setCurrentMediaItemIndex current playlist + * MediaItemData.Builder#setPeriods periods} in the media item, if set, and the period at the + * specified index must be part of the {@linkplain #setCurrentMediaItemIndex current media * item}. * * @param currentPeriodIndex The current period index, or {@link C#INDEX_UNSET} to assume the - * first period of the current playlist item is played. + * first period of the current media item is played. * @return This builder. */ @CanIgnoreReturnValue @@ -640,7 +640,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { * C#INDEX_UNSET}. * *

    Ads indices can only be set if there is a corresponding {@link AdPlaybackState} defined - * in the current {@linkplain PlaylistItem.Builder#setPeriods period}. + * in the current {@linkplain MediaItemData.Builder#setPeriods period}. * * @param adGroupIndex The current ad group index, or {@link C#INDEX_UNSET} if no ad is * playing. @@ -866,9 +866,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final boolean newlyRenderedFirstFrame; /** The most recent timed metadata. */ public final Metadata timedMetadata; - /** The playlist items. */ - public final ImmutableList playlistItems; - /** The {@link Timeline} derived from the {@linkplain #playlistItems playlist items}. */ + /** The media items in the playlist. */ + public final ImmutableList playlist; + /** The {@link Timeline} derived from the {@link #playlist}. */ public final Timeline timeline; /** The playlist {@link MediaMetadata}. */ public final MediaMetadata playlistMetadata; @@ -876,7 +876,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final int currentMediaItemIndex; /** * The current period index, or {@link C#INDEX_UNSET} to assume the first period of the current - * playlist item is played. + * media item is played. */ public final int currentPeriodIndex; /** The current ad group index, or {@link C#INDEX_UNSET} if no ad is playing. */ @@ -1002,7 +1002,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.surfaceSize = builder.surfaceSize; this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame; this.timedMetadata = builder.timedMetadata; - this.playlistItems = builder.playlistItems; + this.playlist = builder.playlist; this.timeline = builder.timeline; this.playlistMetadata = builder.playlistMetadata; this.currentMediaItemIndex = builder.currentMediaItemIndex; @@ -1059,7 +1059,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { && surfaceSize.equals(state.surfaceSize) && newlyRenderedFirstFrame == state.newlyRenderedFirstFrame && timedMetadata.equals(state.timedMetadata) - && playlistItems.equals(state.playlistItems) + && playlist.equals(state.playlist) && playlistMetadata.equals(state.playlistMetadata) && currentMediaItemIndex == state.currentMediaItemIndex && currentPeriodIndex == state.currentPeriodIndex @@ -1105,7 +1105,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { result = 31 * result + surfaceSize.hashCode(); result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0); result = 31 * result + timedMetadata.hashCode(); - result = 31 * result + playlistItems.hashCode(); + result = 31 * result + playlist.hashCode(); result = 31 * result + playlistMetadata.hashCode(); result = 31 * result + currentMediaItemIndex; result = 31 * result + currentPeriodIndex; @@ -1125,28 +1125,28 @@ public abstract class SimpleBasePlayer extends BasePlayer { private static final class PlaylistTimeline extends Timeline { - private final ImmutableList playlistItems; + private final ImmutableList playlist; private final int[] firstPeriodIndexByWindowIndex; private final int[] windowIndexByPeriodIndex; private final HashMap periodIndexByUid; - public PlaylistTimeline(ImmutableList playlistItems) { - int playlistItemCount = playlistItems.size(); - this.playlistItems = playlistItems; - this.firstPeriodIndexByWindowIndex = new int[playlistItemCount]; + public PlaylistTimeline(ImmutableList playlist) { + int mediaItemCount = playlist.size(); + this.playlist = playlist; + this.firstPeriodIndexByWindowIndex = new int[mediaItemCount]; int periodCount = 0; - for (int i = 0; i < playlistItemCount; i++) { - PlaylistItem playlistItem = playlistItems.get(i); + for (int i = 0; i < mediaItemCount; i++) { + MediaItemData mediaItemData = playlist.get(i); firstPeriodIndexByWindowIndex[i] = periodCount; - periodCount += getPeriodCountInPlaylistItem(playlistItem); + periodCount += getPeriodCountInMediaItem(mediaItemData); } this.windowIndexByPeriodIndex = new int[periodCount]; this.periodIndexByUid = new HashMap<>(); int periodIndex = 0; - for (int i = 0; i < playlistItemCount; i++) { - PlaylistItem playlistItem = playlistItems.get(i); - for (int j = 0; j < getPeriodCountInPlaylistItem(playlistItem); j++) { - periodIndexByUid.put(playlistItem.getPeriodUid(j), periodIndex); + for (int i = 0; i < mediaItemCount; i++) { + MediaItemData mediaItemData = playlist.get(i); + for (int j = 0; j < getPeriodCountInMediaItem(mediaItemData); j++) { + periodIndexByUid.put(mediaItemData.getPeriodUid(j), periodIndex); windowIndexByPeriodIndex[periodIndex] = i; periodIndex++; } @@ -1155,7 +1155,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public int getWindowCount() { - return playlistItems.size(); + return playlist.size(); } @Override @@ -1184,7 +1184,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { - return playlistItems + return playlist .get(windowIndex) .getWindow(firstPeriodIndexByWindowIndex[windowIndex], window); } @@ -1204,7 +1204,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { public Period getPeriod(int periodIndex, Period period, boolean setIds) { int windowIndex = windowIndexByPeriodIndex[periodIndex]; int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; - return playlistItems.get(windowIndex).getPeriod(windowIndex, periodIndexInWindow, period); + return playlist.get(windowIndex).getPeriod(windowIndex, periodIndexInWindow, period); } @Override @@ -1217,21 +1217,22 @@ public abstract class SimpleBasePlayer extends BasePlayer { public Object getUidOfPeriod(int periodIndex) { int windowIndex = windowIndexByPeriodIndex[periodIndex]; int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; - return playlistItems.get(windowIndex).getPeriodUid(periodIndexInWindow); + return playlist.get(windowIndex).getPeriodUid(periodIndexInWindow); } - private static int getPeriodCountInPlaylistItem(PlaylistItem playlistItem) { - return playlistItem.periods.isEmpty() ? 1 : playlistItem.periods.size(); + private static int getPeriodCountInMediaItem(MediaItemData mediaItemData) { + return mediaItemData.periods.isEmpty() ? 1 : mediaItemData.periods.size(); } } /** - * An immutable description of a playlist item, containing both static setup information like - * {@link MediaItem} and dynamic data that is generally read from the media like the duration. + * An immutable description of an item in the playlist, containing both static setup information + * like {@link MediaItem} and dynamic data that is generally read from the media like the + * duration. */ - protected static final class PlaylistItem { + protected static final class MediaItemData { - /** A builder for {@link PlaylistItem} objects. */ + /** A builder for {@link MediaItemData} objects. */ public static final class Builder { private Object uid; @@ -1254,7 +1255,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { /** * Creates the builder. * - * @param uid The unique identifier of the playlist item within a playlist. This value will be + * @param uid The unique identifier of the media item within a playlist. This value will be * set as {@link Timeline.Window#uid} for this item. */ public Builder(Object uid) { @@ -1276,31 +1277,31 @@ public abstract class SimpleBasePlayer extends BasePlayer { periods = ImmutableList.of(); } - private Builder(PlaylistItem playlistItem) { - this.uid = playlistItem.uid; - this.tracks = playlistItem.tracks; - this.mediaItem = playlistItem.mediaItem; - this.mediaMetadata = playlistItem.mediaMetadata; - this.manifest = playlistItem.manifest; - this.liveConfiguration = playlistItem.liveConfiguration; - this.presentationStartTimeMs = playlistItem.presentationStartTimeMs; - this.windowStartTimeMs = playlistItem.windowStartTimeMs; - this.elapsedRealtimeEpochOffsetMs = playlistItem.elapsedRealtimeEpochOffsetMs; - this.isSeekable = playlistItem.isSeekable; - this.isDynamic = playlistItem.isDynamic; - this.defaultPositionUs = playlistItem.defaultPositionUs; - this.durationUs = playlistItem.durationUs; - this.positionInFirstPeriodUs = playlistItem.positionInFirstPeriodUs; - this.isPlaceholder = playlistItem.isPlaceholder; - this.periods = playlistItem.periods; + private Builder(MediaItemData mediaItemData) { + this.uid = mediaItemData.uid; + this.tracks = mediaItemData.tracks; + this.mediaItem = mediaItemData.mediaItem; + this.mediaMetadata = mediaItemData.mediaMetadata; + this.manifest = mediaItemData.manifest; + this.liveConfiguration = mediaItemData.liveConfiguration; + this.presentationStartTimeMs = mediaItemData.presentationStartTimeMs; + this.windowStartTimeMs = mediaItemData.windowStartTimeMs; + this.elapsedRealtimeEpochOffsetMs = mediaItemData.elapsedRealtimeEpochOffsetMs; + this.isSeekable = mediaItemData.isSeekable; + this.isDynamic = mediaItemData.isDynamic; + this.defaultPositionUs = mediaItemData.defaultPositionUs; + this.durationUs = mediaItemData.durationUs; + this.positionInFirstPeriodUs = mediaItemData.positionInFirstPeriodUs; + this.isPlaceholder = mediaItemData.isPlaceholder; + this.periods = mediaItemData.periods; } /** - * Sets the unique identifier of this playlist item within a playlist. + * Sets the unique identifier of this media item within a playlist. * *

    This value will be set as {@link Timeline.Window#uid} for this item. * - * @param uid The unique identifier of this playlist item within a playlist. + * @param uid The unique identifier of this media item within a playlist. * @return This builder. */ @CanIgnoreReturnValue @@ -1310,9 +1311,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the {@link Tracks} of this playlist item. + * Sets the {@link Tracks} of this media item. * - * @param tracks The {@link Tracks} of this playlist item. + * @param tracks The {@link Tracks} of this media item. * @return This builder. */ @CanIgnoreReturnValue @@ -1322,9 +1323,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the {@link MediaItem} for this playlist item. + * Sets the {@link MediaItem}. * - * @param mediaItem The {@link MediaItem} for this playlist item. + * @param mediaItem The {@link MediaItem}. * @return This builder. */ @CanIgnoreReturnValue @@ -1354,9 +1355,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the manifest of the playlist item. + * Sets the manifest of the media item. * - * @param manifest The manifest of the playlist item, or null if not applicable. + * @param manifest The manifest of the media item, or null if not applicable. * @return This builder. */ @CanIgnoreReturnValue @@ -1366,11 +1367,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the active {@link MediaItem.LiveConfiguration}, or null if the playlist item is not - * live. + * Sets the active {@link MediaItem.LiveConfiguration}, or null if the media item is not live. * * @param liveConfiguration The active {@link MediaItem.LiveConfiguration}, or null if the - * playlist item is not live. + * media item is not live. * @return This builder. */ @CanIgnoreReturnValue @@ -1431,9 +1431,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets whether it's possible to seek within this playlist item. + * Sets whether it's possible to seek within this media item. * - * @param isSeekable Whether it's possible to seek within this playlist item. + * @param isSeekable Whether it's possible to seek within this media item. * @return This builder. */ @CanIgnoreReturnValue @@ -1443,9 +1443,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets whether this playlist item may change over time, for example a moving live window. + * Sets whether this media item may change over time, for example a moving live window. * - * @param isDynamic Whether this playlist item may change over time, for example a moving live + * @param isDynamic Whether this media item may change over time, for example a moving live * window. * @return This builder. */ @@ -1456,13 +1456,13 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the default position relative to the start of the playlist item at which to begin + * Sets the default position relative to the start of the media item at which to begin * playback, in microseconds. * *

    The default position must be less or equal to the {@linkplain #setDurationUs duration}, * is set. * - * @param defaultPositionUs The default position relative to the start of the playlist item at + * @param defaultPositionUs The default position relative to the start of the media item at * which to begin playback, in microseconds. * @return This builder. */ @@ -1474,14 +1474,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the duration of the playlist item, in microseconds. + * Sets the duration of the media item, in microseconds. * *

    If both this duration and all {@linkplain #setPeriods period} durations are set, the sum * of this duration and the {@linkplain #setPositionInFirstPeriodUs offset in the first * period} must match the total duration of all periods. * - * @param durationUs The duration of the playlist item, in microseconds, or {@link - * C#TIME_UNSET} if unknown. + * @param durationUs The duration of the media item, in microseconds, or {@link C#TIME_UNSET} + * if unknown. * @return This builder. */ @CanIgnoreReturnValue @@ -1492,11 +1492,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the position of the start of this playlist item relative to the start of the first - * period belonging to it, in microseconds. + * Sets the position of the start of this media item relative to the start of the first period + * belonging to it, in microseconds. * - * @param positionInFirstPeriodUs The position of the start of this playlist item relative to - * the start of the first period belonging to it, in microseconds. + * @param positionInFirstPeriodUs The position of the start of this media item relative to the + * start of the first period belonging to it, in microseconds. * @return This builder. */ @CanIgnoreReturnValue @@ -1507,11 +1507,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets whether this playlist item contains placeholder information because the real - * information has yet to be loaded. + * Sets whether this media item contains placeholder information because the real information + * has yet to be loaded. * - * @param isPlaceholder Whether this playlist item contains placeholder information because - * the real information has yet to be loaded. + * @param isPlaceholder Whether this media item contains placeholder information because the + * real information has yet to be loaded. * @return This builder. */ @CanIgnoreReturnValue @@ -1521,15 +1521,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the list of {@linkplain PeriodData periods} in this playlist item. + * Sets the list of {@linkplain PeriodData periods} in this media item. * *

    All periods must have unique {@linkplain PeriodData.Builder#setUid UIDs} and only the * last period is allowed to have an unset {@linkplain PeriodData.Builder#setDurationUs * duration}. * - * @param periods The list of {@linkplain PeriodData periods} in this playlist item, or an - * empty list to assume a single period without ads and the same duration as the playlist - * item. + * @param periods The list of {@linkplain PeriodData periods} in this media item, or an empty + * list to assume a single period without ads and the same duration as the media item. * @return This builder. */ @CanIgnoreReturnValue @@ -1545,17 +1544,17 @@ public abstract class SimpleBasePlayer extends BasePlayer { return this; } - /** Builds the {@link PlaylistItem}. */ - public PlaylistItem build() { - return new PlaylistItem(this); + /** Builds the {@link MediaItemData}. */ + public MediaItemData build() { + return new MediaItemData(this); } } - /** The unique identifier of this playlist item. */ + /** The unique identifier of this media item. */ public final Object uid; - /** The {@link Tracks} of this playlist item. */ + /** The {@link Tracks} of this media item. */ public final Tracks tracks; - /** The {@link MediaItem} for this playlist item. */ + /** The {@link MediaItem}. */ public final MediaItem mediaItem; /** * The {@link MediaMetadata}, including static data from the {@link MediaItem#mediaMetadata @@ -1565,9 +1564,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { * {@link Format#metadata Formats}. */ @Nullable public final MediaMetadata mediaMetadata; - /** The manifest of the playlist item, or null if not applicable. */ + /** The manifest of the media item, or null if not applicable. */ @Nullable public final Object manifest; - /** The active {@link MediaItem.LiveConfiguration}, or null if the playlist item is not live. */ + /** The active {@link MediaItem.LiveConfiguration}, or null if the media item is not live. */ @Nullable public final MediaItem.LiveConfiguration liveConfiguration; /** * The start time of the live presentation, in milliseconds since the Unix epoch, or {@link @@ -1585,37 +1584,37 @@ public abstract class SimpleBasePlayer extends BasePlayer { * applicable. */ public final long elapsedRealtimeEpochOffsetMs; - /** Whether it's possible to seek within this playlist item. */ + /** Whether it's possible to seek within this media item. */ public final boolean isSeekable; - /** Whether this playlist item may change over time, for example a moving live window. */ + /** Whether this media item may change over time, for example a moving live window. */ public final boolean isDynamic; /** - * The default position relative to the start of the playlist item at which to begin playback, - * in microseconds. + * The default position relative to the start of the media item at which to begin playback, in + * microseconds. */ public final long defaultPositionUs; - /** The duration of the playlist item, in microseconds, or {@link C#TIME_UNSET} if unknown. */ + /** The duration of the media item, in microseconds, or {@link C#TIME_UNSET} if unknown. */ public final long durationUs; /** - * The position of the start of this playlist item relative to the start of the first period + * The position of the start of this media item relative to the start of the first period * belonging to it, in microseconds. */ public final long positionInFirstPeriodUs; /** - * Whether this playlist item contains placeholder information because the real information has - * yet to be loaded. + * Whether this media item contains placeholder information because the real information has yet + * to be loaded. */ public final boolean isPlaceholder; /** - * The list of {@linkplain PeriodData periods} in this playlist item, or an empty list to assume - * a single period without ads and the same duration as the playlist item. + * The list of {@linkplain PeriodData periods} in this media item, or an empty list to assume a + * single period without ads and the same duration as the media item. */ public final ImmutableList periods; private final long[] periodPositionInWindowUs; private final MediaMetadata combinedMediaMetadata; - private PlaylistItem(Builder builder) { + private MediaItemData(Builder builder) { if (builder.liveConfiguration == null) { checkArgument(builder.presentationStartTimeMs == C.TIME_UNSET); checkArgument(builder.windowStartTimeMs == C.TIME_UNSET); @@ -1665,26 +1664,26 @@ public abstract class SimpleBasePlayer extends BasePlayer { if (this == o) { return true; } - if (!(o instanceof PlaylistItem)) { + if (!(o instanceof MediaItemData)) { return false; } - PlaylistItem playlistItem = (PlaylistItem) o; - return this.uid.equals(playlistItem.uid) - && this.tracks.equals(playlistItem.tracks) - && this.mediaItem.equals(playlistItem.mediaItem) - && Util.areEqual(this.mediaMetadata, playlistItem.mediaMetadata) - && Util.areEqual(this.manifest, playlistItem.manifest) - && Util.areEqual(this.liveConfiguration, playlistItem.liveConfiguration) - && this.presentationStartTimeMs == playlistItem.presentationStartTimeMs - && this.windowStartTimeMs == playlistItem.windowStartTimeMs - && this.elapsedRealtimeEpochOffsetMs == playlistItem.elapsedRealtimeEpochOffsetMs - && this.isSeekable == playlistItem.isSeekable - && this.isDynamic == playlistItem.isDynamic - && this.defaultPositionUs == playlistItem.defaultPositionUs - && this.durationUs == playlistItem.durationUs - && this.positionInFirstPeriodUs == playlistItem.positionInFirstPeriodUs - && this.isPlaceholder == playlistItem.isPlaceholder - && this.periods.equals(playlistItem.periods); + MediaItemData mediaItemData = (MediaItemData) o; + return this.uid.equals(mediaItemData.uid) + && this.tracks.equals(mediaItemData.tracks) + && this.mediaItem.equals(mediaItemData.mediaItem) + && Util.areEqual(this.mediaMetadata, mediaItemData.mediaMetadata) + && Util.areEqual(this.manifest, mediaItemData.manifest) + && Util.areEqual(this.liveConfiguration, mediaItemData.liveConfiguration) + && this.presentationStartTimeMs == mediaItemData.presentationStartTimeMs + && this.windowStartTimeMs == mediaItemData.windowStartTimeMs + && this.elapsedRealtimeEpochOffsetMs == mediaItemData.elapsedRealtimeEpochOffsetMs + && this.isSeekable == mediaItemData.isSeekable + && this.isDynamic == mediaItemData.isDynamic + && this.defaultPositionUs == mediaItemData.defaultPositionUs + && this.durationUs == mediaItemData.durationUs + && this.positionInFirstPeriodUs == mediaItemData.positionInFirstPeriodUs + && this.isPlaceholder == mediaItemData.isPlaceholder + && this.periods.equals(mediaItemData.periods); } @Override @@ -1733,7 +1732,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { } private Timeline.Period getPeriod( - int windowIndex, int periodIndexInPlaylistItem, Timeline.Period period) { + int windowIndex, int periodIndexInMediaItem, Timeline.Period period) { if (periods.isEmpty()) { period.set( /* id= */ uid, @@ -1744,7 +1743,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { AdPlaybackState.NONE, isPlaceholder); } else { - PeriodData periodData = periods.get(periodIndexInPlaylistItem); + PeriodData periodData = periods.get(periodIndexInMediaItem); Object periodId = periodData.uid; Object periodUid = Pair.create(uid, periodId); period.set( @@ -1752,18 +1751,18 @@ public abstract class SimpleBasePlayer extends BasePlayer { periodUid, windowIndex, periodData.durationUs, - periodPositionInWindowUs[periodIndexInPlaylistItem], + periodPositionInWindowUs[periodIndexInMediaItem], periodData.adPlaybackState, periodData.isPlaceholder); } return period; } - private Object getPeriodUid(int periodIndexInPlaylistItem) { + private Object getPeriodUid(int periodIndexInMediaItem) { if (periods.isEmpty()) { return uid; } - Object periodId = periods.get(periodIndexInPlaylistItem).uid; + Object periodId = periods.get(periodIndexInMediaItem).uid; return Pair.create(uid, periodId); } @@ -1787,7 +1786,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { } } - /** Data describing the properties of a period inside a {@link PlaylistItem}. */ + /** Data describing the properties of a period inside a {@link MediaItemData}. */ protected static final class PeriodData { /** A builder for {@link PeriodData} objects. */ @@ -1801,7 +1800,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { /** * Creates the builder. * - * @param uid The unique identifier of the period within its playlist item. + * @param uid The unique identifier of the period within its media item. */ public Builder(Object uid) { this.uid = uid; @@ -1818,9 +1817,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Sets the unique identifier of the period within its playlist item. + * Sets the unique identifier of the period within its media item. * - * @param uid The unique identifier of the period within its playlist item. + * @param uid The unique identifier of the period within its media item. * @return This builder. */ @CanIgnoreReturnValue @@ -1832,7 +1831,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { /** * Sets the total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. * - *

    Only the last period in a playlist item can have an unknown duration. + *

    Only the last period in a media item can have an unknown duration. * * @param durationUs The total duration of the period, in microseconds, or {@link * C#TIME_UNSET} if unknown. @@ -1878,11 +1877,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { } } - /** The unique identifier of the period within its playlist item. */ + /** The unique identifier of the period within its media item. */ public final Object uid; /** * The total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. Only - * the last period in a playlist item can have an unknown duration. + * the last period in a media item can have an unknown duration. */ public final long durationUs; /** @@ -2539,8 +2538,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { if (timelineChanged) { @Player.TimelineChangeReason - int timelineChangeReason = - getTimelineChangeReason(previousState.playlistItems, newState.playlistItems); + int timelineChangeReason = getTimelineChangeReason(previousState.playlist, newState.playlist); listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newState.timeline, timelineChangeReason)); @@ -2567,7 +2565,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { MediaItem mediaItem = state.timeline.isEmpty() ? null - : state.playlistItems.get(state.currentMediaItemIndex).mediaItem; + : state.playlist.get(state.currentMediaItemIndex).mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); @@ -2795,15 +2793,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { } private static Tracks getCurrentTracksInternal(State state) { - return state.playlistItems.isEmpty() + return state.playlist.isEmpty() ? Tracks.EMPTY - : state.playlistItems.get(state.currentMediaItemIndex).tracks; + : state.playlist.get(state.currentMediaItemIndex).tracks; } private static MediaMetadata getMediaMetadataInternal(State state) { - return state.playlistItems.isEmpty() + return state.playlist.isEmpty() ? MediaMetadata.EMPTY - : state.playlistItems.get(state.currentMediaItemIndex).combinedMediaMetadata; + : state.playlist.get(state.currentMediaItemIndex).combinedMediaMetadata; } private static int getCurrentPeriodIndexInternal(State state, Timeline.Window window) { @@ -2817,7 +2815,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { } private static @Player.TimelineChangeReason int getTimelineChangeReason( - List previousPlaylist, List newPlaylist) { + List previousPlaylist, List newPlaylist) { if (previousPlaylist.size() != newPlaylist.size()) { return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; } @@ -2835,11 +2833,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { // We were asked to report a discontinuity. return newState.positionDiscontinuityReason; } - if (previousState.playlistItems.isEmpty()) { - // First change from an empty timeline is not reported as a discontinuity. + if (previousState.playlist.isEmpty()) { + // First change from an empty playlist is not reported as a discontinuity. return C.INDEX_UNSET; } - if (newState.playlistItems.isEmpty()) { + if (newState.playlist.isEmpty()) { // The playlist became empty. return Player.DISCONTINUITY_REASON_REMOVE; } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 41e49bd0ab..0995639a66 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -117,15 +117,15 @@ public class SimpleBasePlayerTest { .setTimedMetadata(new Metadata()) .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( /* adsId= */ new Object(), - /* adGroupTimesUs= */ 555, + /* adGroupTimesUs...= */ 555, 666)) .build())) .build())) @@ -149,9 +149,9 @@ public class SimpleBasePlayerTest { } @Test - public void playlistItemBuildUpon_build_isEqual() { - SimpleBasePlayer.PlaylistItem playlistItem = - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + public void mediaItemDataBuildUpon_build_isEqual() { + SimpleBasePlayer.MediaItemData mediaItemData = + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setTracks( new Tracks( ImmutableList.of( @@ -179,10 +179,10 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()).build())) .build(); - SimpleBasePlayer.PlaylistItem newPlaylistItem = playlistItem.buildUpon().build(); + SimpleBasePlayer.MediaItemData newMediaItemData = mediaItemData.buildUpon().build(); - assertThat(newPlaylistItem).isEqualTo(playlistItem); - assertThat(newPlaylistItem.hashCode()).isEqualTo(playlistItem.hashCode()); + assertThat(newMediaItemData).isEqualTo(mediaItemData); + assertThat(newMediaItemData.hashCode()).isEqualTo(mediaItemData.hashCode()); } @Test @@ -192,7 +192,7 @@ public class SimpleBasePlayerTest { .setIsPlaceholder(true) .setDurationUs(600_000) .setAdPlaybackState( - new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666)) .build(); SimpleBasePlayer.PeriodData newPeriodData = periodData.buildUpon().build(); @@ -227,16 +227,16 @@ public class SimpleBasePlayerTest { Size surfaceSize = new Size(480, 360); DeviceInfo deviceInfo = new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7); - ImmutableList playlist = + ImmutableList playlist = ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( - /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + /* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666)) .build())) .build()); MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build(); @@ -319,7 +319,7 @@ public class SimpleBasePlayerTest { assertThat(state.surfaceSize).isEqualTo(surfaceSize); assertThat(state.newlyRenderedFirstFrame).isTrue(); assertThat(state.timedMetadata).isEqualTo(timedMetadata); - assertThat(state.playlistItems).isEqualTo(playlist); + assertThat(state.playlist).isEqualTo(playlist); assertThat(state.playlistMetadata).isEqualTo(playlistMetadata); assertThat(state.currentMediaItemIndex).isEqualTo(1); assertThat(state.currentPeriodIndex).isEqualTo(1); @@ -376,8 +376,9 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) + .build())) .setCurrentMediaItemIndex(2) .build()); } @@ -390,8 +391,9 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) + .build())) .setCurrentPeriodIndex(2) .build()); } @@ -404,8 +406,9 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) + .build())) .setCurrentMediaItemIndex(0) .setCurrentPeriodIndex(1) .build()); @@ -419,14 +422,14 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( /* adsId= */ new Object(), - /* adGroupTimesUs= */ 123)) + /* adGroupTimesUs...= */ 123)) .build())) .build())) .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) @@ -441,14 +444,14 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( /* adsId= */ new Object(), - /* adGroupTimesUs= */ 123) + /* adGroupTimesUs...= */ 123) .withAdCount( /* adGroupIndex= */ 0, /* adCount= */ 2)) .build())) @@ -473,7 +476,7 @@ public class SimpleBasePlayerTest { } @Test - public void stateBuilderBuild_multiplePlaylistItemsWithSameIds_throwsException() { + public void stateBuilderBuild_multipleMediaItemsWithSameIds_throwsException() { Object uid = new Object(); assertThrows( @@ -482,8 +485,8 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(uid).build(), - new SimpleBasePlayer.PlaylistItem.Builder(uid).build())) + new SimpleBasePlayer.MediaItemData.Builder(uid).build(), + new SimpleBasePlayer.MediaItemData.Builder(uid).build())) .build()); } @@ -524,7 +527,7 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) .setContentPositionMs(4000) .setPlayWhenReady(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) .setPlaybackState(Player.STATE_READY) @@ -546,7 +549,7 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build())) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) .setContentPositionMs(4000) .setPlaybackState(Player.STATE_BUFFERING) .build(); @@ -566,14 +569,14 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( /* adsId= */ new Object(), - /* adGroupTimesUs= */ 123) + /* adGroupTimesUs...= */ 123) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)) .build())) .build())) @@ -600,14 +603,14 @@ public class SimpleBasePlayerTest { new SimpleBasePlayer.State.Builder() .setPlaylist( ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) .setAdPlaybackState( new AdPlaybackState( /* adsId= */ new Object(), - /* adGroupTimesUs= */ 123) + /* adGroupTimesUs...= */ 123) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)) .build())) .build())) @@ -624,7 +627,7 @@ public class SimpleBasePlayerTest { } @Test - public void playlistItemBuilderBuild_setsCorrectValues() { + public void mediaItemDataBuilderBuild_setsCorrectValues() { Object uid = new Object(); Tracks tracks = new Tracks( @@ -642,8 +645,8 @@ public class SimpleBasePlayerTest { ImmutableList periods = ImmutableList.of(new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()).build()); - SimpleBasePlayer.PlaylistItem playlistItem = - new SimpleBasePlayer.PlaylistItem.Builder(uid) + SimpleBasePlayer.MediaItemData mediaItemData = + new SimpleBasePlayer.MediaItemData.Builder(uid) .setTracks(tracks) .setMediaItem(mediaItem) .setMediaMetadata(mediaMetadata) @@ -661,61 +664,61 @@ public class SimpleBasePlayerTest { .setPeriods(periods) .build(); - assertThat(playlistItem.uid).isEqualTo(uid); - assertThat(playlistItem.tracks).isEqualTo(tracks); - assertThat(playlistItem.mediaItem).isEqualTo(mediaItem); - assertThat(playlistItem.mediaMetadata).isEqualTo(mediaMetadata); - assertThat(playlistItem.manifest).isEqualTo(manifest); - assertThat(playlistItem.liveConfiguration).isEqualTo(liveConfiguration); - assertThat(playlistItem.presentationStartTimeMs).isEqualTo(12); - assertThat(playlistItem.windowStartTimeMs).isEqualTo(23); - assertThat(playlistItem.elapsedRealtimeEpochOffsetMs).isEqualTo(10234); - assertThat(playlistItem.isSeekable).isTrue(); - assertThat(playlistItem.isDynamic).isTrue(); - assertThat(playlistItem.defaultPositionUs).isEqualTo(456_789); - assertThat(playlistItem.durationUs).isEqualTo(500_000); - assertThat(playlistItem.positionInFirstPeriodUs).isEqualTo(100_000); - assertThat(playlistItem.isPlaceholder).isTrue(); - assertThat(playlistItem.periods).isEqualTo(periods); + assertThat(mediaItemData.uid).isEqualTo(uid); + assertThat(mediaItemData.tracks).isEqualTo(tracks); + assertThat(mediaItemData.mediaItem).isEqualTo(mediaItem); + assertThat(mediaItemData.mediaMetadata).isEqualTo(mediaMetadata); + assertThat(mediaItemData.manifest).isEqualTo(manifest); + assertThat(mediaItemData.liveConfiguration).isEqualTo(liveConfiguration); + assertThat(mediaItemData.presentationStartTimeMs).isEqualTo(12); + assertThat(mediaItemData.windowStartTimeMs).isEqualTo(23); + assertThat(mediaItemData.elapsedRealtimeEpochOffsetMs).isEqualTo(10234); + assertThat(mediaItemData.isSeekable).isTrue(); + assertThat(mediaItemData.isDynamic).isTrue(); + assertThat(mediaItemData.defaultPositionUs).isEqualTo(456_789); + assertThat(mediaItemData.durationUs).isEqualTo(500_000); + assertThat(mediaItemData.positionInFirstPeriodUs).isEqualTo(100_000); + assertThat(mediaItemData.isPlaceholder).isTrue(); + assertThat(mediaItemData.periods).isEqualTo(periods); } @Test - public void playlistItemBuilderBuild_presentationStartTimeIfNotLive_throwsException() { + public void mediaItemDataBuilderBuild_presentationStartTimeIfNotLive_throwsException() { assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPresentationStartTimeMs(12) .build()); } @Test - public void playlistItemBuilderBuild_windowStartTimeIfNotLive_throwsException() { + public void mediaItemDataBuilderBuild_windowStartTimeIfNotLive_throwsException() { assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setWindowStartTimeMs(12) .build()); } @Test - public void playlistItemBuilderBuild_elapsedEpochOffsetIfNotLive_throwsException() { + public void mediaItemDataBuilderBuild_elapsedEpochOffsetIfNotLive_throwsException() { assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setElapsedRealtimeEpochOffsetMs(12) .build()); } @Test public void - playlistItemBuilderBuild_windowStartTimeLessThanPresentationStartTime_throwsException() { + mediaItemDataBuilderBuild_windowStartTimeLessThanPresentationStartTime_throwsException() { assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setLiveConfiguration(MediaItem.LiveConfiguration.UNSET) .setWindowStartTimeMs(12) .setPresentationStartTimeMs(13) @@ -723,13 +726,13 @@ public class SimpleBasePlayerTest { } @Test - public void playlistItemBuilderBuild_multiplePeriodsWithSameUid_throwsException() { + public void mediaItemDataBuilderBuild_multiplePeriodsWithSameUid_throwsException() { Object uid = new Object(); assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setPeriods( ImmutableList.of( new SimpleBasePlayer.PeriodData.Builder(uid).build(), @@ -738,11 +741,11 @@ public class SimpleBasePlayerTest { } @Test - public void playlistItemBuilderBuild_defaultPositionGreaterThanDuration_throwsException() { + public void mediaItemDataBuilderBuild_defaultPositionGreaterThanDuration_throwsException() { assertThrows( IllegalArgumentException.class, () -> - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setDefaultPositionUs(16) .setDurationUs(15) .build()); @@ -752,7 +755,7 @@ public class SimpleBasePlayerTest { public void periodDataBuilderBuild_setsCorrectValues() { Object uid = new Object(); AdPlaybackState adPlaybackState = - new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666); + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666); SimpleBasePlayer.PeriodData periodData = new SimpleBasePlayer.PeriodData.Builder(uid) @@ -795,7 +798,7 @@ public class SimpleBasePlayerTest { SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456; SimpleBasePlayer.PositionSupplier contentBufferedPositionSupplier = () -> 499; SimpleBasePlayer.PositionSupplier totalBufferedPositionSupplier = () -> 567; - Object playlistItemUid = new Object(); + Object mediaItemUid = new Object(); Object periodUid = new Object(); Tracks tracks = new Tracks( @@ -811,10 +814,10 @@ public class SimpleBasePlayerTest { Size surfaceSize = new Size(480, 360); MediaItem.LiveConfiguration liveConfiguration = new MediaItem.LiveConfiguration.Builder().setTargetOffsetMs(2000).build(); - ImmutableList playlist = + ImmutableList playlist = ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(playlistItemUid) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid) .setTracks(tracks) .setMediaItem(mediaItem) .setMediaMetadata(mediaMetadata) @@ -836,7 +839,7 @@ public class SimpleBasePlayerTest { .setDurationUs(600_000) .setAdPlaybackState( new AdPlaybackState( - /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666)) + /* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666)) .build())) .build()); State state = @@ -955,7 +958,7 @@ public class SimpleBasePlayerTest { assertThat(window.liveConfiguration).isEqualTo(liveConfiguration); assertThat(window.manifest).isEqualTo(manifest); assertThat(window.mediaItem).isEqualTo(mediaItem); - assertThat(window.uid).isEqualTo(playlistItemUid); + assertThat(window.uid).isEqualTo(mediaItemUid); Timeline.Period period = timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true); assertThat(period.durationUs).isEqualTo(C.TIME_UNSET); @@ -981,10 +984,10 @@ public class SimpleBasePlayerTest { SimpleBasePlayer.PositionSupplier totalBufferedPositionSupplier = () -> 567; SimpleBasePlayer.PositionSupplier adPositionSupplier = () -> 321; SimpleBasePlayer.PositionSupplier adBufferedPositionSupplier = () -> 345; - ImmutableList playlist = + ImmutableList playlist = ImmutableList.of( - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()) + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) .setDurationUs(500_000) .setPeriods( ImmutableList.of( @@ -993,7 +996,9 @@ public class SimpleBasePlayerTest { .setDurationUs(600_000) .setAdPlaybackState( new AdPlaybackState( - /* adsId= */ new Object(), /* adGroupTimesUs= */ 555, 666) + /* adsId= */ new Object(), /* adGroupTimesUs...= */ + 555, + 666) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1) .withAdDurationsUs( @@ -1058,8 +1063,8 @@ public class SimpleBasePlayerTest { public void invalidateState_updatesStateAndInformsListeners() throws Exception { Object mediaItemUid0 = new Object(); MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem0 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); + SimpleBasePlayer.MediaItemData mediaItemData0 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); State state1 = new State.Builder() .setAvailableCommands(new Commands.Builder().addAllCommands().build()) @@ -1085,7 +1090,7 @@ public class SimpleBasePlayerTest { .setDeviceInfo(DeviceInfo.UNKNOWN) .setDeviceVolume(0) .setIsDeviceMuted(false) - .setPlaylist(ImmutableList.of(playlistItem0)) + .setPlaylist(ImmutableList.of(mediaItemData0)) .setPlaylistMetadata(MediaMetadata.EMPTY) .setCurrentMediaItemIndex(0) .setContentPositionMs(8_000) @@ -1101,8 +1106,8 @@ public class SimpleBasePlayerTest { /* adaptiveSupported= */ true, /* trackSupport= */ new int[] {C.FORMAT_HANDLED}, /* trackSelected= */ new boolean[] {true}))); - SimpleBasePlayer.PlaylistItem playlistItem1 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1) + SimpleBasePlayer.MediaItemData mediaItemData1 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1) .setMediaItem(mediaItem1) .setMediaMetadata(mediaMetadata) .setTracks(tracks) @@ -1163,7 +1168,7 @@ public class SimpleBasePlayerTest { .setSurfaceSize(surfaceSize) .setNewlyRenderedFirstFrame(true) .setTimedMetadata(timedMetadata) - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setPlaylistMetadata(playlistMetadata) .setCurrentMediaItemIndex(1) .setContentPositionMs(12_000) @@ -1304,20 +1309,20 @@ public class SimpleBasePlayerTest { } @Test - public void invalidateState_withPlaylistItemDetailChange_reportsTimelineSourceUpdate() { + public void invalidateState_withMediaItemDetailChange_reportsTimelineSourceUpdate() { Object mediaItemUid0 = new Object(); - SimpleBasePlayer.PlaylistItem playlistItem0 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).build(); + SimpleBasePlayer.MediaItemData mediaItemData0 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid0).build(); Object mediaItemUid1 = new Object(); - SimpleBasePlayer.PlaylistItem playlistItem1 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).build(); + SimpleBasePlayer.MediaItemData mediaItemData1 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1).build(); State state1 = - new State.Builder().setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)).build(); - SimpleBasePlayer.PlaylistItem playlistItem1Updated = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setDurationUs(10_000).build(); + new State.Builder().setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)).build(); + SimpleBasePlayer.MediaItemData mediaItemData1Updated = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1).setDurationUs(10_000).build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1Updated)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1Updated)) .build(); AtomicBoolean returnState2 = new AtomicBoolean(); SimpleBasePlayer player = @@ -1343,21 +1348,21 @@ public class SimpleBasePlayerTest { public void invalidateState_withCurrentMediaItemRemoval_reportsDiscontinuityReasonRemoved() { Object mediaItemUid0 = new Object(); MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem0 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); + SimpleBasePlayer.MediaItemData mediaItemData0 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid0).setMediaItem(mediaItem0).build(); Object mediaItemUid1 = new Object(); MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); - SimpleBasePlayer.PlaylistItem playlistItem1 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + SimpleBasePlayer.MediaItemData mediaItemData1 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setCurrentMediaItemIndex(1) .setContentPositionMs(5000) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0)) + .setPlaylist(ImmutableList.of(mediaItemData0)) .setCurrentMediaItemIndex(0) .setContentPositionMs(2000) .build(); @@ -1409,24 +1414,24 @@ public class SimpleBasePlayerTest { invalidateState_withTransitionFromEndOfItem_reportsDiscontinuityReasonAutoTransition() { Object mediaItemUid0 = new Object(); MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem0 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0) + SimpleBasePlayer.MediaItemData mediaItemData0 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid0) .setMediaItem(mediaItem0) .setDurationUs(50_000) .build(); Object mediaItemUid1 = new Object(); MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); - SimpleBasePlayer.PlaylistItem playlistItem1 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + SimpleBasePlayer.MediaItemData mediaItemData1 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setCurrentMediaItemIndex(0) .setContentPositionMs(50) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setCurrentMediaItemIndex(1) .setContentPositionMs(10) .build(); @@ -1476,24 +1481,24 @@ public class SimpleBasePlayerTest { public void invalidateState_withTransitionFromMiddleOfItem_reportsDiscontinuityReasonSkip() { Object mediaItemUid0 = new Object(); MediaItem mediaItem0 = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem0 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid0) + SimpleBasePlayer.MediaItemData mediaItemData0 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid0) .setMediaItem(mediaItem0) .setDurationUs(50_000) .build(); Object mediaItemUid1 = new Object(); MediaItem mediaItem1 = new MediaItem.Builder().setMediaId("1").build(); - SimpleBasePlayer.PlaylistItem playlistItem1 = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); + SimpleBasePlayer.MediaItemData mediaItemData1 = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid1).setMediaItem(mediaItem1).build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setCurrentMediaItemIndex(0) .setContentPositionMs(20) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem0, playlistItem1)) + .setPlaylist(ImmutableList.of(mediaItemData0, mediaItemData1)) .setCurrentMediaItemIndex(1) .setContentPositionMs(10) .build(); @@ -1544,20 +1549,20 @@ public class SimpleBasePlayerTest { public void invalidateState_withRepeatingItem_reportsDiscontinuityReasonAutoTransition() { Object mediaItemUid = new Object(); MediaItem mediaItem = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid) + SimpleBasePlayer.MediaItemData mediaItemData = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid) .setMediaItem(mediaItem) .setDurationUs(5_000_000) .build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(5_000) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(0) .build(); @@ -1607,20 +1612,20 @@ public class SimpleBasePlayerTest { public void invalidateState_withDiscontinuityInsideItem_reportsDiscontinuityReasonInternal() { Object mediaItemUid = new Object(); MediaItem mediaItem = new MediaItem.Builder().setMediaId("0").build(); - SimpleBasePlayer.PlaylistItem playlistItem = - new SimpleBasePlayer.PlaylistItem.Builder(mediaItemUid) + SimpleBasePlayer.MediaItemData mediaItemData = + new SimpleBasePlayer.MediaItemData.Builder(mediaItemUid) .setMediaItem(mediaItem) .setDurationUs(5_000_000) .build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(1_000) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(3_000) .build(); @@ -1668,17 +1673,17 @@ public class SimpleBasePlayerTest { @Test public void invalidateState_withMinorPositionDrift_doesNotReportsDiscontinuity() { - SimpleBasePlayer.PlaylistItem playlistItem = - new SimpleBasePlayer.PlaylistItem.Builder(/* uid= */ new Object()).build(); + SimpleBasePlayer.MediaItemData mediaItemData = + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(); State state1 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(1_000) .build(); State state2 = new State.Builder() - .setPlaylist(ImmutableList.of(playlistItem)) + .setPlaylist(ImmutableList.of(mediaItemData)) .setCurrentMediaItemIndex(0) .setContentPositionMs(1_500) .build(); From 8246587b00aceb833d57d73bccee2c098712aa48 Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 30 Nov 2022 21:29:53 +0000 Subject: [PATCH 033/104] Parse and set bitrates in `Ac3Reader` PiperOrigin-RevId: 492003800 (cherry picked from commit 5f73984823b943d750f41519d431ad3b12dada65) --- .../android/exoplayer2/audio/Ac3Util.java | 21 +++++++++++++++++-- .../exoplayer2/extractor/ts/Ac3Reader.java | 10 +++++++-- .../extractordumps/ts/sample.ac3.0.dump | 2 ++ .../ts/sample.ac3.unknown_length.dump | 2 ++ .../extractordumps/ts/sample.eac3.0.dump | 1 + .../ts/sample.eac3.unknown_length.dump | 1 + .../extractordumps/ts/sample_ac3.ps.0.dump | 2 ++ .../ts/sample_ac3.ps.unknown_length.dump | 2 ++ .../extractordumps/ts/sample_ac3.ts.0.dump | 2 ++ .../extractordumps/ts/sample_ac3.ts.1.dump | 2 ++ .../extractordumps/ts/sample_ac3.ts.2.dump | 2 ++ .../extractordumps/ts/sample_ac3.ts.3.dump | 2 ++ .../ts/sample_ac3.ts.unknown_length.dump | 2 ++ .../extractordumps/ts/sample_ait.ts.0.dump | 1 + .../ts/sample_ait.ts.unknown_length.dump | 1 + .../extractordumps/ts/sample_eac3.ts.0.dump | 1 + .../extractordumps/ts/sample_eac3.ts.1.dump | 1 + .../extractordumps/ts/sample_eac3.ts.2.dump | 1 + .../extractordumps/ts/sample_eac3.ts.3.dump | 1 + .../ts/sample_eac3.ts.unknown_length.dump | 1 + .../ts/sample_eac3joc.ec3.0.dump | 1 + .../ts/sample_eac3joc.ec3.unknown_length.dump | 1 + .../ts/sample_eac3joc.ts.0.dump | 1 + .../ts/sample_eac3joc.ts.1.dump | 1 + .../ts/sample_eac3joc.ts.2.dump | 1 + .../ts/sample_eac3joc.ts.3.dump | 1 + .../ts/sample_eac3joc.ts.unknown_length.dump | 1 + 27 files changed, 61 insertions(+), 4 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 07f2f710c6..4ef68bf345 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -78,6 +78,8 @@ public final class Ac3Util { public final int frameSize; /** Number of audio samples in the frame. */ public final int sampleCount; + /** The bitrate of audio samples. */ + public final int bitrate; private SyncFrameInfo( @Nullable String mimeType, @@ -85,13 +87,15 @@ public final class Ac3Util { int channelCount, int sampleRate, int frameSize, - int sampleCount) { + int sampleCount, + int bitrate) { this.mimeType = mimeType; this.streamType = streamType; this.channelCount = channelCount; this.sampleRate = sampleRate; this.frameSize = frameSize; this.sampleCount = sampleCount; + this.bitrate = bitrate; } } @@ -259,6 +263,7 @@ public final class Ac3Util { int sampleCount; boolean lfeon; int channelCount; + int bitrate; if (isEac3) { // Subsection E.1.2. data.skipBits(16); // syncword @@ -291,6 +296,7 @@ public final class Ac3Util { sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; } sampleCount = AUDIO_SAMPLES_PER_AUDIO_BLOCK * audioBlocks; + bitrate = calculateEac3Bitrate(frameSize, sampleRate, audioBlocks); acmod = data.readBits(3); lfeon = data.readBit(); channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0); @@ -446,6 +452,7 @@ public final class Ac3Util { mimeType = null; } int frmsizecod = data.readBits(6); + bitrate = BITRATE_BY_HALF_FRMSIZECOD[frmsizecod / 2] * 1000; frameSize = getAc3SyncframeSize(fscod, frmsizecod); data.skipBits(5 + 3); // bsid, bsmod acmod = data.readBits(3); @@ -465,7 +472,7 @@ public final class Ac3Util { channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0); } return new SyncFrameInfo( - mimeType, streamType, channelCount, sampleRate, frameSize, sampleCount); + mimeType, streamType, channelCount, sampleRate, frameSize, sampleCount, bitrate); } /** @@ -587,5 +594,15 @@ public final class Ac3Util { } } + /** + * Derived from the formula defined in F.6.2.2 to calculate data_rate for the (E-)AC3 bitstream. + * Note: The formula is based on frmsiz read from the spec. We already do some modifications to it + * when deriving frameSize from the read value. The formula used here is adapted to accommodate + * that modification. + */ + private static int calculateEac3Bitrate(int frameSize, int sampleRate, int audioBlocks) { + return (frameSize * sampleRate) / (audioBlocks * 32); + } + private Ac3Util() {} } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 2b7d0a1d75..01b86edae8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -207,14 +208,19 @@ public final class Ac3Reader implements ElementaryStreamReader { || frameInfo.channelCount != format.channelCount || frameInfo.sampleRate != format.sampleRate || !Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) { - format = + Format.Builder formatBuilder = new Format.Builder() .setId(formatId) .setSampleMimeType(frameInfo.mimeType) .setChannelCount(frameInfo.channelCount) .setSampleRate(frameInfo.sampleRate) .setLanguage(language) - .build(); + .setPeakBitrate(frameInfo.bitrate); + // AC3 has constant bitrate, so averageBitrate = peakBitrate + if (MimeTypes.AUDIO_AC3.equals(frameInfo.mimeType)) { + formatBuilder.setAverageBitrate(frameInfo.bitrate); + } + format = formatBuilder.build(); output.format(format); } sampleSize = frameInfo.frameSize; diff --git a/testdata/src/test/assets/extractordumps/ts/sample.ac3.0.dump b/testdata/src/test/assets/extractordumps/ts/sample.ac3.0.dump index 3f582caedd..8aad7940f2 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample.ac3.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample.ac3.0.dump @@ -7,6 +7,8 @@ track 0: total output bytes = 13281 sample count = 8 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 0 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample.ac3.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample.ac3.unknown_length.dump index 3f582caedd..8aad7940f2 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample.ac3.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample.ac3.unknown_length.dump @@ -7,6 +7,8 @@ track 0: total output bytes = 13281 sample count = 8 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 0 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample.eac3.0.dump b/testdata/src/test/assets/extractordumps/ts/sample.eac3.0.dump index f3d9d3997d..f8be0e618c 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample.eac3.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample.eac3.0.dump @@ -7,6 +7,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 6000000 id = 0 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample.eac3.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample.eac3.unknown_length.dump index f3d9d3997d..f8be0e618c 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample.eac3.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample.eac3.unknown_length.dump @@ -7,6 +7,7 @@ track 0: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 6000000 id = 0 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.0.dump index 27d0c450fd..143245058f 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.0.dump @@ -10,6 +10,8 @@ track 189: total output bytes = 1252 sample count = 3 format 0: + averageBitrate = 96000 + peakBitrate = 96000 id = 189 sampleMimeType = audio/ac3 channelCount = 1 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.unknown_length.dump index 960882156b..62c215256f 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ps.unknown_length.dump @@ -7,6 +7,8 @@ track 189: total output bytes = 1252 sample count = 3 format 0: + averageBitrate = 96000 + peakBitrate = 96000 id = 189 sampleMimeType = audio/ac3 channelCount = 1 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.0.dump index 561963e10c..f3ac4e2018 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.0.dump @@ -10,6 +10,8 @@ track 1900: total output bytes = 13281 sample count = 8 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 1/1900 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.1.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.1.dump index d778af898d..9f141492b2 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.1.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.1.dump @@ -10,6 +10,8 @@ track 1900: total output bytes = 10209 sample count = 6 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 1/1900 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.2.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.2.dump index f48ba43854..e6cea3993f 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.2.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.2.dump @@ -10,6 +10,8 @@ track 1900: total output bytes = 7137 sample count = 4 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 1/1900 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.3.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.3.dump index 997d7a6b02..da9814ead3 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.3.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.3.dump @@ -10,6 +10,8 @@ track 1900: total output bytes = 0 sample count = 0 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 1/1900 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.unknown_length.dump index a98cb798cb..f992ac64e8 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ac3.ts.unknown_length.dump @@ -7,6 +7,8 @@ track 1900: total output bytes = 13281 sample count = 8 format 0: + averageBitrate = 384000 + peakBitrate = 384000 id = 1/1900 sampleMimeType = audio/ac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.0.dump index 355b403293..3a305ed662 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.0.dump @@ -7,6 +7,7 @@ track 330: total output bytes = 9928 sample count = 19 format 0: + peakBitrate = 128000 id = 1031/330 sampleMimeType = audio/eac3 channelCount = 2 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.unknown_length.dump index 355b403293..3a305ed662 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_ait.ts.unknown_length.dump @@ -7,6 +7,7 @@ track 330: total output bytes = 9928 sample count = 19 format 0: + peakBitrate = 128000 id = 1031/330 sampleMimeType = audio/eac3 channelCount = 2 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.0.dump index dfc89c5f19..2ccaceef7f 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.0.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 6000000 id = 1/1900 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.1.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.1.dump index c06294df2c..ccf162fc31 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.1.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.1.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 168000 sample count = 42 format 0: + peakBitrate = 6000000 id = 1/1900 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.2.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.2.dump index 9104607498..286fc25b16 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.2.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.2.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 96000 sample count = 24 format 0: + peakBitrate = 6000000 id = 1/1900 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.3.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.3.dump index c490b7eca8..cfbfae20b6 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.3.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.3.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 0 sample count = 0 format 0: + peakBitrate = 6000000 id = 1/1900 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.unknown_length.dump index 0aae4097a7..4bb8650d72 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3.ts.unknown_length.dump @@ -7,6 +7,7 @@ track 1900: total output bytes = 216000 sample count = 54 format 0: + peakBitrate = 6000000 id = 1/1900 sampleMimeType = audio/eac3 channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.0.dump index f8888698bd..5a8f918105 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.0.dump @@ -7,6 +7,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640000 id = 0 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.unknown_length.dump index f8888698bd..5a8f918105 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ec3.unknown_length.dump @@ -7,6 +7,7 @@ track 0: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640000 id = 0 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.0.dump index a3cf812691..2fc2f49280 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.0.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640000 id = 1/1900 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.1.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.1.dump index 77951bd767..771c5216c5 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.1.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.1.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 112640 sample count = 44 format 0: + peakBitrate = 640000 id = 1/1900 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.2.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.2.dump index 0354754df2..452d8eea13 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.2.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.2.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 56320 sample count = 22 format 0: + peakBitrate = 640000 id = 1/1900 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.3.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.3.dump index 742d87e271..8da152a79f 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.3.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.3.dump @@ -10,6 +10,7 @@ track 1900: total output bytes = 5120 sample count = 2 format 0: + peakBitrate = 640000 id = 1/1900 sampleMimeType = audio/eac3-joc channelCount = 6 diff --git a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.unknown_length.dump index 269dd63593..82ce1d24bb 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_eac3joc.ts.unknown_length.dump @@ -7,6 +7,7 @@ track 1900: total output bytes = 163840 sample count = 64 format 0: + peakBitrate = 640000 id = 1/1900 sampleMimeType = audio/eac3-joc channelCount = 6 From 8ec46cd4dcecdda763f4c635471096dccb9e1a8b Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 1 Dec 2022 08:30:19 +0000 Subject: [PATCH 034/104] Add media type to MediaMetadata This helps to denote what type of content or folder the metadata describes. PiperOrigin-RevId: 492123690 (cherry picked from commit 1ac72de551a07d37c3b80d96028463244407a5b4) --- .../android/exoplayer2/MediaMetadata.java | 215 +++++++++++++++++- .../android/exoplayer2/MediaMetadataTest.java | 2 + 2 files changed, 214 insertions(+), 3 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index da59d7c09e..77bbcb9de8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -76,6 +76,7 @@ public final class MediaMetadata implements Bundleable { @Nullable private CharSequence genre; @Nullable private CharSequence compilation; @Nullable private CharSequence station; + @Nullable private @MediaType Integer mediaType; @Nullable private Bundle extras; public Builder() {} @@ -111,6 +112,7 @@ public final class MediaMetadata implements Bundleable { this.genre = mediaMetadata.genre; this.compilation = mediaMetadata.compilation; this.station = mediaMetadata.station; + this.mediaType = mediaMetadata.mediaType; this.extras = mediaMetadata.extras; } @@ -381,6 +383,13 @@ public final class MediaMetadata implements Bundleable { return this; } + /** Sets the {@link MediaType}. */ + @CanIgnoreReturnValue + public Builder setMediaType(@Nullable @MediaType Integer mediaType) { + this.mediaType = mediaType; + return this; + } + /** Sets the extras {@link Bundle}. */ @CanIgnoreReturnValue public Builder setExtras(@Nullable Bundle extras) { @@ -524,6 +533,9 @@ public final class MediaMetadata implements Bundleable { if (mediaMetadata.station != null) { setStation(mediaMetadata.station); } + if (mediaMetadata.mediaType != null) { + setMediaType(mediaMetadata.mediaType); + } if (mediaMetadata.extras != null) { setExtras(mediaMetadata.extras); } @@ -537,12 +549,185 @@ public final class MediaMetadata implements Bundleable { } } + /** + * The type of content described by the media item. + * + *

    One of {@link #MEDIA_TYPE_MIXED}, {@link #MEDIA_TYPE_MUSIC}, {@link + * #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}, {@link #MEDIA_TYPE_PODCAST_EPISODE}, {@link + * #MEDIA_TYPE_RADIO_STATION}, {@link #MEDIA_TYPE_NEWS}, {@link #MEDIA_TYPE_VIDEO}, {@link + * #MEDIA_TYPE_TRAILER}, {@link #MEDIA_TYPE_MOVIE}, {@link #MEDIA_TYPE_TV_SHOW}, {@link + * #MEDIA_TYPE_ALBUM}, {@link #MEDIA_TYPE_ARTIST}, {@link #MEDIA_TYPE_GENRE}, {@link + * #MEDIA_TYPE_PLAYLIST}, {@link #MEDIA_TYPE_YEAR}, {@link #MEDIA_TYPE_AUDIO_BOOK}, {@link + * #MEDIA_TYPE_PODCAST}, {@link #MEDIA_TYPE_TV_CHANNEL}, {@link #MEDIA_TYPE_TV_SERIES}, {@link + * #MEDIA_TYPE_TV_SEASON}, {@link #MEDIA_TYPE_FOLDER_MIXED}, {@link #MEDIA_TYPE_FOLDER_ALBUMS}, + * {@link #MEDIA_TYPE_FOLDER_ARTISTS}, {@link #MEDIA_TYPE_FOLDER_GENRES}, {@link + * #MEDIA_TYPE_FOLDER_PLAYLISTS}, {@link #MEDIA_TYPE_FOLDER_YEARS}, {@link + * #MEDIA_TYPE_FOLDER_AUDIO_BOOKS}, {@link #MEDIA_TYPE_FOLDER_PODCASTS}, {@link + * #MEDIA_TYPE_FOLDER_TV_CHANNELS}, {@link #MEDIA_TYPE_FOLDER_TV_SERIES}, {@link + * #MEDIA_TYPE_FOLDER_TV_SHOWS}, {@link #MEDIA_TYPE_FOLDER_RADIO_STATIONS}, {@link + * #MEDIA_TYPE_FOLDER_NEWS}, {@link #MEDIA_TYPE_FOLDER_VIDEOS}, {@link + * #MEDIA_TYPE_FOLDER_TRAILERS} or {@link #MEDIA_TYPE_FOLDER_MOVIES}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + MEDIA_TYPE_MIXED, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_AUDIO_BOOK_CHAPTER, + MEDIA_TYPE_PODCAST_EPISODE, + MEDIA_TYPE_RADIO_STATION, + MEDIA_TYPE_NEWS, + MEDIA_TYPE_VIDEO, + MEDIA_TYPE_TRAILER, + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_TV_SHOW, + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_GENRE, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_YEAR, + MEDIA_TYPE_AUDIO_BOOK, + MEDIA_TYPE_PODCAST, + MEDIA_TYPE_TV_CHANNEL, + MEDIA_TYPE_TV_SERIES, + MEDIA_TYPE_TV_SEASON, + MEDIA_TYPE_FOLDER_MIXED, + MEDIA_TYPE_FOLDER_ALBUMS, + MEDIA_TYPE_FOLDER_ARTISTS, + MEDIA_TYPE_FOLDER_GENRES, + MEDIA_TYPE_FOLDER_PLAYLISTS, + MEDIA_TYPE_FOLDER_YEARS, + MEDIA_TYPE_FOLDER_AUDIO_BOOKS, + MEDIA_TYPE_FOLDER_PODCASTS, + MEDIA_TYPE_FOLDER_TV_CHANNELS, + MEDIA_TYPE_FOLDER_TV_SERIES, + MEDIA_TYPE_FOLDER_TV_SHOWS, + MEDIA_TYPE_FOLDER_RADIO_STATIONS, + MEDIA_TYPE_FOLDER_NEWS, + MEDIA_TYPE_FOLDER_VIDEOS, + MEDIA_TYPE_FOLDER_TRAILERS, + MEDIA_TYPE_FOLDER_MOVIES, + }) + public @interface MediaType {} + + /** Media of undetermined type or a mix of multiple {@linkplain MediaType media types}. */ + public static final int MEDIA_TYPE_MIXED = 0; + /** {@link MediaType} for music. */ + public static final int MEDIA_TYPE_MUSIC = 1; + /** {@link MediaType} for an audio book chapter. */ + public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2; + /** {@link MediaType} for a podcast episode. */ + public static final int MEDIA_TYPE_PODCAST_EPISODE = 3; + /** {@link MediaType} for a radio station. */ + public static final int MEDIA_TYPE_RADIO_STATION = 4; + /** {@link MediaType} for news. */ + public static final int MEDIA_TYPE_NEWS = 5; + /** {@link MediaType} for a video. */ + public static final int MEDIA_TYPE_VIDEO = 6; + /** {@link MediaType} for a movie trailer. */ + public static final int MEDIA_TYPE_TRAILER = 7; + /** {@link MediaType} for a movie. */ + public static final int MEDIA_TYPE_MOVIE = 8; + /** {@link MediaType} for a TV show. */ + public static final int MEDIA_TYPE_TV_SHOW = 9; + /** + * {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) belonging to an + * album. + */ + public static final int MEDIA_TYPE_ALBUM = 10; + /** + * {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same + * artist. + */ + public static final int MEDIA_TYPE_ARTIST = 11; + /** + * {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) of the same + * genre. + */ + public static final int MEDIA_TYPE_GENRE = 12; + /** + * {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) forming a + * playlist. + */ + public static final int MEDIA_TYPE_PLAYLIST = 13; + /** + * {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same + * year. + */ + public static final int MEDIA_TYPE_YEAR = 14; + /** + * {@link MediaType} for a group of items forming an audio book. Items in this group are typically + * of type {@link #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}. + */ + public static final int MEDIA_TYPE_AUDIO_BOOK = 15; + /** + * {@link MediaType} for a group of items belonging to a podcast. Items in this group are + * typically of type {@link #MEDIA_TYPE_PODCAST_EPISODE}. + */ + public static final int MEDIA_TYPE_PODCAST = 16; + /** + * {@link MediaType} for a group of items that are part of a TV channel. Items in this group are + * typically of type {@link #MEDIA_TYPE_TV_SHOW}, {@link #MEDIA_TYPE_TV_SERIES} or {@link + * #MEDIA_TYPE_MOVIE}. + */ + public static final int MEDIA_TYPE_TV_CHANNEL = 17; + /** + * {@link MediaType} for a group of items that are part of a TV series. Items in this group are + * typically of type {@link #MEDIA_TYPE_TV_SHOW} or {@link #MEDIA_TYPE_TV_SEASON}. + */ + public static final int MEDIA_TYPE_TV_SERIES = 18; + /** + * {@link MediaType} for a group of items that are part of a TV series. Items in this group are + * typically of type {@link #MEDIA_TYPE_TV_SHOW}. + */ + public static final int MEDIA_TYPE_TV_SEASON = 19; + /** {@link MediaType} for a folder with mixed or undetermined content. */ + public static final int MEDIA_TYPE_FOLDER_MIXED = 20; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_ALBUM albums}. */ + public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21; + /** {@link MediaType} for a folder containing {@linkplain #FIELD_ARTIST artists}. */ + public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_GENRE genres}. */ + public static final int MEDIA_TYPE_FOLDER_GENRES = 23; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PLAYLIST playlists}. */ + public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_YEAR years}. */ + public static final int MEDIA_TYPE_FOLDER_YEARS = 25; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_AUDIO_BOOK audio books}. */ + public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PODCAST podcasts}. */ + public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_CHANNEL TV channels}. */ + public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SERIES TV series}. */ + public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SHOW TV shows}. */ + public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30; + /** + * {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_RADIO_STATION radio + * stations}. + */ + public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_NEWS news}. */ + public static final int MEDIA_TYPE_FOLDER_NEWS = 32; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_VIDEO videos}. */ + public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TRAILER movie trailers}. */ + public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34; + /** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_MOVIE movies}. */ + public static final int MEDIA_TYPE_FOLDER_MOVIES = 35; + /** * The folder type of the media item. * *

    This can be used as the type of a browsable bluetooth folder (see section 6.10.2.2 of the Bluetooth * AVRCP 1.6.2). + * + *

    One of {@link #FOLDER_TYPE_NONE}, {@link #FOLDER_TYPE_MIXED}, {@link #FOLDER_TYPE_TITLES}, + * {@link #FOLDER_TYPE_ALBUMS}, {@link #FOLDER_TYPE_ARTISTS}, {@link #FOLDER_TYPE_GENRES}, {@link + * #FOLDER_TYPE_PLAYLISTS} or {@link #FOLDER_TYPE_YEARS}. */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // with Kotlin usages from before TYPE_USE was added. @@ -583,6 +768,17 @@ public final class MediaMetadata implements Bundleable { * *

    Values sourced from the ID3 v2.4 specification (See section 4.14 of * https://id3.org/id3v2.4.0-frames). + * + *

    One of {@link #PICTURE_TYPE_OTHER}, {@link #PICTURE_TYPE_FILE_ICON}, {@link + * #PICTURE_TYPE_FILE_ICON_OTHER}, {@link #PICTURE_TYPE_FRONT_COVER}, {@link + * #PICTURE_TYPE_BACK_COVER}, {@link #PICTURE_TYPE_LEAFLET_PAGE}, {@link #PICTURE_TYPE_MEDIA}, + * {@link #PICTURE_TYPE_LEAD_ARTIST_PERFORMER}, {@link #PICTURE_TYPE_ARTIST_PERFORMER}, {@link + * #PICTURE_TYPE_CONDUCTOR}, {@link #PICTURE_TYPE_BAND_ORCHESTRA}, {@link #PICTURE_TYPE_COMPOSER}, + * {@link #PICTURE_TYPE_LYRICIST}, {@link #PICTURE_TYPE_RECORDING_LOCATION}, {@link + * #PICTURE_TYPE_DURING_RECORDING}, {@link #PICTURE_TYPE_DURING_PERFORMANCE}, {@link + * #PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE}, {@link #PICTURE_TYPE_A_BRIGHT_COLORED_FISH}, {@link + * #PICTURE_TYPE_ILLUSTRATION}, {@link #PICTURE_TYPE_BAND_ARTIST_LOGO} or {@link + * #PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}. */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // with Kotlin usages from before TYPE_USE was added. @@ -724,6 +920,8 @@ public final class MediaMetadata implements Bundleable { @Nullable public final CharSequence compilation; /** Optional name of the station streaming the media. */ @Nullable public final CharSequence station; + /** Optional {@link MediaType}. */ + @Nullable public final @MediaType Integer mediaType; /** * Optional extras {@link Bundle}. @@ -765,6 +963,7 @@ public final class MediaMetadata implements Bundleable { this.genre = builder.genre; this.compilation = builder.compilation; this.station = builder.station; + this.mediaType = builder.mediaType; this.extras = builder.extras; } @@ -811,7 +1010,8 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(totalDiscCount, that.totalDiscCount) && Util.areEqual(genre, that.genre) && Util.areEqual(compilation, that.compilation) - && Util.areEqual(station, that.station); + && Util.areEqual(station, that.station) + && Util.areEqual(mediaType, that.mediaType); } @Override @@ -846,7 +1046,8 @@ public final class MediaMetadata implements Bundleable { totalDiscCount, genre, compilation, - station); + station, + mediaType); } // Bundleable implementation. @@ -886,7 +1087,8 @@ public final class MediaMetadata implements Bundleable { FIELD_GENRE, FIELD_COMPILATION, FIELD_STATION, - FIELD_EXTRAS + FIELD_MEDIA_TYPE, + FIELD_EXTRAS, }) private @interface FieldNumber {} @@ -921,6 +1123,7 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_COMPILATION = 28; private static final int FIELD_ARTWORK_DATA_TYPE = 29; private static final int FIELD_STATION = 30; + private static final int FIELD_MEDIA_TYPE = 31; private static final int FIELD_EXTRAS = 1000; @Override @@ -987,6 +1190,9 @@ public final class MediaMetadata implements Bundleable { if (artworkDataType != null) { bundle.putInt(keyForField(FIELD_ARTWORK_DATA_TYPE), artworkDataType); } + if (mediaType != null) { + bundle.putInt(keyForField(FIELD_MEDIA_TYPE), mediaType); + } if (extras != null) { bundle.putBundle(keyForField(FIELD_EXTRAS), extras); } @@ -1068,6 +1274,9 @@ public final class MediaMetadata implements Bundleable { if (bundle.containsKey(keyForField(FIELD_TOTAL_DISC_COUNT))) { builder.setTotalDiscCount(bundle.getInt(keyForField(FIELD_TOTAL_DISC_COUNT))); } + if (bundle.containsKey(keyForField(FIELD_MEDIA_TYPE))) { + builder.setMediaType(bundle.getInt(keyForField(FIELD_MEDIA_TYPE))); + } return builder.build(); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 5bf5feaa62..2c1df81e40 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -64,6 +64,7 @@ public class MediaMetadataTest { assertThat(mediaMetadata.genre).isNull(); assertThat(mediaMetadata.compilation).isNull(); assertThat(mediaMetadata.station).isNull(); + assertThat(mediaMetadata.mediaType).isNull(); assertThat(mediaMetadata.extras).isNull(); } @@ -149,6 +150,7 @@ public class MediaMetadataTest { .setGenre("Pop") .setCompilation("Amazing songs.") .setStation("radio station") + .setMediaType(MediaMetadata.MEDIA_TYPE_MIXED) .setExtras(extras) .build(); } From 2bfced9bbcfd1a94c9cbbadc695667bf3a882346 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 1 Dec 2022 08:34:14 +0000 Subject: [PATCH 035/104] Add support for most setters in SimpleBasePlayer This adds the forwarding logic for most setters in SimpleExoPlayer in the same style as the existing logic for setPlayWhenReady. This change doesn't implement the setters for modifying media items, seeking and releasing yet as they require additional handling that goes beyond the repeated implementation pattern in this change. PiperOrigin-RevId: 492124399 (cherry picked from commit e598a17b3628e1179fa4219ca3212407fb3fdeb1) --- .../android/exoplayer2/SimpleBasePlayer.java | 509 +++++- .../google/android/exoplayer2/util/Size.java | 3 + .../exoplayer2/SimpleBasePlayerTest.java | 1511 ++++++++++++++++- 3 files changed, 1955 insertions(+), 68 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 7a6e231a30..c5c9a10dc5 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.usToMs; import static java.lang.Math.max; +import android.graphics.Rect; import android.os.Looper; import android.os.SystemClock; import android.util.Pair; @@ -2039,6 +2040,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!state.availableCommands.contains(Player.COMMAND_PLAY_PAUSE)) { return; @@ -2091,8 +2093,20 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void prepare() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_PREPARE)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handlePrepare(), + /* placeholderStateSupplier= */ () -> + state + .buildUpon() + .setPlayerError(null) + .setPlaybackState(state.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING) + .build()); } @Override @@ -2117,8 +2131,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setRepeatMode(@Player.RepeatMode int repeatMode) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_REPEAT_MODE)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetRepeatMode(repeatMode), + /* placeholderStateSupplier= */ () -> state.buildUpon().setRepeatMode(repeatMode).build()); } @Override @@ -2130,8 +2151,16 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setShuffleModeEnabled(boolean shuffleModeEnabled) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_SHUFFLE_MODE)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetShuffleModeEnabled(shuffleModeEnabled), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setShuffleModeEnabled(shuffleModeEnabled).build()); } @Override @@ -2152,6 +2181,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { throw new IllegalStateException(); } + @Override + protected final void repeatCurrentMediaItem() { + // TODO: implement. + throw new IllegalStateException(); + } + @Override public final long getSeekBackIncrement() { verifyApplicationThreadAndInitState(); @@ -2172,8 +2207,16 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setPlaybackParameters(PlaybackParameters playbackParameters) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_SPEED_AND_PITCH)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetPlaybackParameters(playbackParameters), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setPlaybackParameters(playbackParameters).build()); } @Override @@ -2184,14 +2227,30 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void stop() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_STOP)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleStop(), + /* placeholderStateSupplier= */ () -> + state + .buildUpon() + .setPlaybackState(Player.STATE_IDLE) + .setTotalBufferedDurationMs(PositionSupplier.ZERO) + .setContentBufferedPositionMs(state.contentPositionMsSupplier) + .setAdBufferedPositionMs(state.adPositionMsSupplier) + .build()); } @Override public final void stop(boolean reset) { - // TODO: implement. - throw new IllegalStateException(); + stop(); + if (reset) { + clearMediaItems(); + } } @Override @@ -2214,8 +2273,16 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setTrackSelectionParameters(TrackSelectionParameters parameters) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetTrackSelectionParameters(parameters), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setTrackSelectionParameters(parameters).build()); } @Override @@ -2232,8 +2299,16 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setPlaylistMetadata(MediaMetadata mediaMetadata) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_MEDIA_ITEMS_METADATA)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetPlaylistMetadata(mediaMetadata), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setPlaylistMetadata(mediaMetadata).build()); } @Override @@ -2325,8 +2400,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setVolume(float volume) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VOLUME)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetVolume(volume), + /* placeholderStateSupplier= */ () -> state.buildUpon().setVolume(volume).build()); } @Override @@ -2335,58 +2417,122 @@ public abstract class SimpleBasePlayer extends BasePlayer { return state.volume; } - @Override - public final void clearVideoSurface() { - // TODO: implement. - throw new IllegalStateException(); - } - - @Override - public final void clearVideoSurface(@Nullable Surface surface) { - // TODO: implement. - throw new IllegalStateException(); - } - @Override public final void setVideoSurface(@Nullable Surface surface) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + return; + } + if (surface == null) { + clearVideoSurface(); + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetVideoOutput(surface), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setSurfaceSize(Size.UNKNOWN).build()); } @Override public final void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - // TODO: implement. - throw new IllegalStateException(); - } - - @Override - public final void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + return; + } + if (surfaceHolder == null) { + clearVideoSurface(); + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetVideoOutput(surfaceHolder), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setSurfaceSize(getSurfaceHolderSize(surfaceHolder)).build()); } @Override public final void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { - // TODO: implement. - throw new IllegalStateException(); - } - - @Override - public final void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + return; + } + if (surfaceView == null) { + clearVideoSurface(); + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetVideoOutput(surfaceView), + /* placeholderStateSupplier= */ () -> + state + .buildUpon() + .setSurfaceSize(getSurfaceHolderSize(surfaceView.getHolder())) + .build()); } @Override public final void setVideoTextureView(@Nullable TextureView textureView) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + return; + } + if (textureView == null) { + clearVideoSurface(); + return; + } + Size surfaceSize; + if (textureView.isAvailable()) { + surfaceSize = new Size(textureView.getWidth(), textureView.getHeight()); + } else { + surfaceSize = Size.ZERO; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetVideoOutput(textureView), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setSurfaceSize(surfaceSize).build()); + } + + @Override + public final void clearVideoSurface() { + clearVideoOutput(/* videoOutput= */ null); + } + + @Override + public final void clearVideoSurface(@Nullable Surface surface) { + clearVideoOutput(surface); + } + + @Override + public final void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { + clearVideoOutput(surfaceHolder); + } + + @Override + public final void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { + clearVideoOutput(surfaceView); } @Override public final void clearVideoTextureView(@Nullable TextureView textureView) { - // TODO: implement. - throw new IllegalStateException(); + clearVideoOutput(textureView); + } + + private void clearVideoOutput(@Nullable Object videoOutput) { + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleClearVideoOutput(videoOutput), + /* placeholderStateSupplier= */ () -> state.buildUpon().setSurfaceSize(Size.ZERO).build()); } @Override @@ -2427,26 +2573,56 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setDeviceVolume(int volume) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_SET_DEVICE_VOLUME)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetDeviceVolume(volume), + /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build()); } @Override public final void increaseDeviceVolume() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleIncreaseDeviceVolume(), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build()); } @Override public final void decreaseDeviceVolume() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleDecreaseDeviceVolume(), + /* placeholderStateSupplier= */ () -> + state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build()); } @Override public final void setDeviceMuted(boolean muted) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetDeviceMuted(muted), + /* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build()); } /** @@ -2500,22 +2676,217 @@ public abstract class SimpleBasePlayer extends BasePlayer { } /** - * Handles calls to set {@link State#playWhenReady}. + * Handles calls to {@link Player#setPlayWhenReady}, {@link Player#play} and {@link Player#pause}. * - *

    Will only be called if {@link Player.Command#COMMAND_PLAY_PAUSE} is available. + *

    Will only be called if {@link Player#COMMAND_PLAY_PAUSE} is available. * * @param playWhenReady The requested {@link State#playWhenReady} * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. - * @see Player#setPlayWhenReady(boolean) - * @see Player#play() - * @see Player#pause() */ @ForOverride protected ListenableFuture handleSetPlayWhenReady(boolean playWhenReady) { throw new IllegalStateException(); } + /** + * Handles calls to {@link Player#prepare}. + * + *

    Will only be called if {@link Player#COMMAND_PREPARE} is available. + * + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handlePrepare() { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#stop}. + * + *

    Will only be called if {@link Player#COMMAND_STOP} is available. + * + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleStop() { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setRepeatMode}. + * + *

    Will only be called if {@link Player#COMMAND_SET_REPEAT_MODE} is available. + * + * @param repeatMode The requested {@link RepeatMode}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetRepeatMode(@RepeatMode int repeatMode) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setShuffleModeEnabled}. + * + *

    Will only be called if {@link Player#COMMAND_SET_SHUFFLE_MODE} is available. + * + * @param shuffleModeEnabled Whether shuffle mode was requested to be enabled. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setPlaybackParameters} or {@link Player#setPlaybackSpeed}. + * + *

    Will only be called if {@link Player#COMMAND_SET_SPEED_AND_PITCH} is available. + * + * @param playbackParameters The requested {@link PlaybackParameters}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetPlaybackParameters(PlaybackParameters playbackParameters) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setTrackSelectionParameters}. + * + *

    Will only be called if {@link Player#COMMAND_SET_TRACK_SELECTION_PARAMETERS} is available. + * + * @param trackSelectionParameters The requested {@link TrackSelectionParameters}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetTrackSelectionParameters( + TrackSelectionParameters trackSelectionParameters) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setPlaylistMetadata}. + * + *

    Will only be called if {@link Player#COMMAND_SET_MEDIA_ITEMS_METADATA} is available. + * + * @param playlistMetadata The requested {@linkplain MediaMetadata playlist metadata}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setVolume}. + * + *

    Will only be called if {@link Player#COMMAND_SET_VOLUME} is available. + * + * @param volume The requested audio volume, with 0 being silence and 1 being unity gain (signal + * unchanged). + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetVolume(@FloatRange(from = 0, to = 1.0) float volume) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setDeviceVolume}. + * + *

    Will only be called if {@link Player#COMMAND_SET_DEVICE_VOLUME} is available. + * + * @param deviceVolume The requested device volume. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetDeviceVolume(@IntRange(from = 0) int deviceVolume) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#increaseDeviceVolume()}. + * + *

    Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available. + * + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleIncreaseDeviceVolume() { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#decreaseDeviceVolume()}. + * + *

    Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available. + * + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleDecreaseDeviceVolume() { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#setDeviceMuted}. + * + *

    Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available. + * + * @param muted Whether the device was requested to be muted. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetDeviceMuted(boolean muted) { + throw new IllegalStateException(); + } + + /** + * Handles calls to set the video output. + * + *

    Will only be called if {@link Player#COMMAND_SET_VIDEO_SURFACE} is available. + * + * @param videoOutput The requested video output. This is either a {@link Surface}, {@link + * SurfaceHolder}, {@link TextureView} or {@link SurfaceView}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetVideoOutput(Object videoOutput) { + throw new IllegalStateException(); + } + + /** + * Handles calls to clear the video output. + * + *

    Will only be called if {@link Player#COMMAND_SET_VIDEO_SURFACE} is available. + * + * @param videoOutput The video output to clear. If null any current output should be cleared. If + * non-null, the output should only be cleared if it matches the provided argument. This is + * either a {@link Surface}, {@link SurfaceHolder}, {@link TextureView} or {@link + * SurfaceView}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { + throw new IllegalStateException(); + } + @SuppressWarnings("deprecation") // Calling deprecated listener methods. @RequiresNonNull("state") private void updateStateAndInformListeners(State newState) { @@ -2974,4 +3345,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { } return C.INDEX_UNSET; } + + private static Size getSurfaceHolderSize(SurfaceHolder surfaceHolder) { + if (!surfaceHolder.getSurface().isValid()) { + return Size.ZERO; + } + Rect surfaceFrame = surfaceHolder.getSurfaceFrame(); + return new Size(surfaceFrame.width(), surfaceFrame.height()); + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Size.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Size.java index c273fbf5ee..bbd8e25893 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Size.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Size.java @@ -28,6 +28,9 @@ public final class Size { public static final Size UNKNOWN = new Size(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET); + /* A static instance to represent a size of zero height and width. */ + public static final Size ZERO = new Size(/* width= */ 0, /* height= */ 0); + private final int width; private final int height; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 0995639a66..ee56a1705b 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -27,8 +27,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.graphics.SurfaceTexture; import android.os.Looper; import android.os.SystemClock; +import android.view.Surface; +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Player.Commands; import com.google.android.exoplayer2.Player.Listener; @@ -1833,17 +1837,18 @@ public class SimpleBasePlayerTest { .setPlayWhenReady( /* playWhenReady= */ true, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE) .build(); - AtomicBoolean stateUpdated = new AtomicBoolean(); SimpleBasePlayer player = new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + @Override protected State getState() { - return stateUpdated.get() ? updatedState : state; + return playerState; } @Override protected ListenableFuture handleSetPlayWhenReady(boolean playWhenReady) { - stateUpdated.set(true); + playerState = updatedState; return Futures.immediateVoidFuture(); } }; @@ -1941,6 +1946,1506 @@ public class SimpleBasePlayerTest { assertThat(callForwarded.get()).isFalse(); } + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void prepare_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_IDLE) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + State updatedState = state.buildUpon().setPlaybackState(Player.STATE_READY).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handlePrepare() { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.prepare(); + + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_READY); + verify(listener).onPlaybackStateChanged(Player.STATE_READY); + verify(listener) + .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_READY); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void prepare_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_IDLE) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + State updatedState = state.buildUpon().setPlaybackState(Player.STATE_READY).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handlePrepare() { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.prepare(); + + // Verify placeholder state and listener calls. + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_BUFFERING); + verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(listener) + .onPlayerStateChanged( + /* playWhenReady= */ false, /* playbackState= */ Player.STATE_BUFFERING); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_READY); + verify(listener).onPlaybackStateChanged(Player.STATE_READY); + verify(listener) + .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_READY); + verifyNoMoreInteractions(listener); + } + + @Test + public void prepare_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_PREPARE).build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handlePrepare() { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.prepare(); + + assertThat(callForwarded.get()).isFalse(); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void stop_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_READY) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + State updatedState = state.buildUpon().setPlaybackState(Player.STATE_IDLE).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleStop() { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.stop(); + + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + verify(listener).onPlaybackStateChanged(Player.STATE_IDLE); + verify(listener) + .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void stop_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_READY) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + // Additionally set the repeat mode to see a difference between the placeholder and new state. + State updatedState = + state + .buildUpon() + .setPlaybackState(Player.STATE_IDLE) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleStop() { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.stop(); + + // Verify placeholder state and listener calls. + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_OFF); + verify(listener).onPlaybackStateChanged(Player.STATE_IDLE); + verify(listener) + .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verifyNoMoreInteractions(listener); + } + + @Test + public void stop_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_STOP).build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleStop() { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.stop(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setRepeatMode_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = state.buildUpon().setRepeatMode(Player.REPEAT_MODE_ALL).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetRepeatMode(@Player.RepeatMode int repeatMode) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setRepeatMode(Player.REPEAT_MODE_ONE); + + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verifyNoMoreInteractions(listener); + } + + @Test + public void setRepeatMode_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a new repeat mode to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setRepeatMode(Player.REPEAT_MODE_ALL).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetRepeatMode(@Player.RepeatMode int repeatMode) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setRepeatMode(Player.REPEAT_MODE_ONE); + + // Verify placeholder state and listener calls. + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ONE); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ONE); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verifyNoMoreInteractions(listener); + } + + @Test + public void setRepeatMode_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_REPEAT_MODE) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetRepeatMode(@Player.RepeatMode int repeatMode) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setRepeatMode(Player.REPEAT_MODE_ONE); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setShuffleModeEnabled_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Also change the repeat mode to ensure the updated state is used. + State updatedState = + state.buildUpon().setShuffleModeEnabled(true).setRepeatMode(Player.REPEAT_MODE_ALL).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setShuffleModeEnabled(true); + + assertThat(player.getShuffleModeEnabled()).isTrue(); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + verify(listener).onShuffleModeEnabledChanged(true); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verifyNoMoreInteractions(listener); + } + + @Test + public void setShuffleModeEnabled_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + // Always return the same state to revert the shuffle mode change. This allows to see a + // difference between the placeholder and new state. + return state; + } + + @Override + protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setShuffleModeEnabled(true); + + // Verify placeholder state and listener calls. + assertThat(player.getShuffleModeEnabled()).isTrue(); + verify(listener).onShuffleModeEnabledChanged(true); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getShuffleModeEnabled()).isFalse(); + verify(listener).onShuffleModeEnabledChanged(false); + verifyNoMoreInteractions(listener); + } + + @Test + public void setShuffleModeEnabled_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_SHUFFLE_MODE) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setShuffleModeEnabled(true); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setPlaybackParameters_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = + state.buildUpon().setPlaybackParameters(new PlaybackParameters(/* speed= */ 3f)).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetPlaybackParameters( + PlaybackParameters playbackParameters) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)); + + assertThat(player.getPlaybackParameters()).isEqualTo(new PlaybackParameters(/* speed= */ 3f)); + verify(listener).onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 3f)); + verifyNoMoreInteractions(listener); + } + + @Test + public void setPlaybackParameters_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a new repeat mode to see a difference between the placeholder and new state. + State updatedState = + state.buildUpon().setPlaybackParameters(new PlaybackParameters(/* speed= */ 3f)).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetPlaybackParameters( + PlaybackParameters playbackParameters) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)); + + // Verify placeholder state and listener calls. + assertThat(player.getPlaybackParameters()).isEqualTo(new PlaybackParameters(/* speed= */ 2f)); + verify(listener).onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 2f)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getPlaybackParameters()).isEqualTo(new PlaybackParameters(/* speed= */ 3f)); + verify(listener).onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 3f)); + verifyNoMoreInteractions(listener); + } + + @Test + public void setPlaybackParameters_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_SPEED_AND_PITCH) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetPlaybackParameters( + PlaybackParameters playbackParameters) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f)); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setTrackSelectionParameters_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + TrackSelectionParameters updatedParameters = + new TrackSelectionParameters.Builder(ApplicationProvider.getApplicationContext()) + .setMaxVideoBitrate(3000) + .build(); + State updatedState = state.buildUpon().setTrackSelectionParameters(updatedParameters).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetTrackSelectionParameters( + TrackSelectionParameters trackSelectionParameters) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setTrackSelectionParameters( + new TrackSelectionParameters.Builder(ApplicationProvider.getApplicationContext()) + .setMaxVideoBitrate(1000) + .build()); + + assertThat(player.getTrackSelectionParameters()).isEqualTo(updatedParameters); + verify(listener).onTrackSelectionParametersChanged(updatedParameters); + verifyNoMoreInteractions(listener); + } + + @Test + public void setTrackSelectionParameters_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set new parameters to see a difference between the placeholder and new state. + TrackSelectionParameters updatedParameters = + new TrackSelectionParameters.Builder(ApplicationProvider.getApplicationContext()) + .setMaxVideoBitrate(3000) + .build(); + State updatedState = state.buildUpon().setTrackSelectionParameters(updatedParameters).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetTrackSelectionParameters( + TrackSelectionParameters trackSelectionParameters) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + TrackSelectionParameters requestedParameters = + new TrackSelectionParameters.Builder(ApplicationProvider.getApplicationContext()) + .setMaxVideoBitrate(3000) + .build(); + player.setTrackSelectionParameters(requestedParameters); + + // Verify placeholder state and listener calls. + assertThat(player.getTrackSelectionParameters()).isEqualTo(requestedParameters); + verify(listener).onTrackSelectionParametersChanged(requestedParameters); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getTrackSelectionParameters()).isEqualTo(updatedParameters); + verify(listener).onTrackSelectionParametersChanged(updatedParameters); + verifyNoMoreInteractions(listener); + } + + @Test + public void setTrackSelectionParameters_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetTrackSelectionParameters( + TrackSelectionParameters trackSelectionParameters) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setTrackSelectionParameters( + new TrackSelectionParameters.Builder(ApplicationProvider.getApplicationContext()) + .setMaxVideoBitrate(1000) + .build()); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setPlaylistMetadata_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + MediaMetadata updatedMetadata = new MediaMetadata.Builder().setArtist("artist").build(); + State updatedState = state.buildUpon().setPlaylistMetadata(updatedMetadata).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setPlaylistMetadata(new MediaMetadata.Builder().setTitle("title").build()); + + assertThat(player.getPlaylistMetadata()).isEqualTo(updatedMetadata); + verify(listener).onPlaylistMetadataChanged(updatedMetadata); + verifyNoMoreInteractions(listener); + } + + @Test + public void setPlaylistMetadata_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set new metadata to see a difference between the placeholder and new state. + MediaMetadata updatedMetadata = new MediaMetadata.Builder().setArtist("artist").build(); + State updatedState = state.buildUpon().setPlaylistMetadata(updatedMetadata).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + MediaMetadata requestedMetadata = new MediaMetadata.Builder().setTitle("title").build(); + player.setPlaylistMetadata(requestedMetadata); + + // Verify placeholder state and listener calls. + assertThat(player.getPlaylistMetadata()).isEqualTo(requestedMetadata); + verify(listener).onPlaylistMetadataChanged(requestedMetadata); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getPlaylistMetadata()).isEqualTo(updatedMetadata); + verify(listener).onPlaylistMetadataChanged(updatedMetadata); + verifyNoMoreInteractions(listener); + } + + @Test + public void setPlaylistMetadata_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_MEDIA_ITEMS_METADATA) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setPlaylistMetadata(new MediaMetadata.Builder().setTitle("title").build()); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setVolume_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = state.buildUpon().setVolume(.8f).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetVolume(float volume) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setVolume(.5f); + + assertThat(player.getVolume()).isEqualTo(.8f); + verify(listener).onVolumeChanged(.8f); + verifyNoMoreInteractions(listener); + } + + @Test + public void setVolume_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a new volume to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setVolume(.8f).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetVolume(float volume) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setVolume(.5f); + + // Verify placeholder state and listener calls. + assertThat(player.getVolume()).isEqualTo(.5f); + verify(listener).onVolumeChanged(.5f); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getVolume()).isEqualTo(.8f); + verify(listener).onVolumeChanged(.8f); + verifyNoMoreInteractions(listener); + } + + @Test + public void setVolume_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_SET_VOLUME).build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetVolume(float volume) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setVolume(.5f); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setDeviceVolume_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = state.buildUpon().setDeviceVolume(6).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetDeviceVolume(int volume) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setDeviceVolume(3); + + assertThat(player.getDeviceVolume()).isEqualTo(6); + verify(listener).onDeviceVolumeChanged(6, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void setDeviceVolume_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Set a new volume to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setDeviceVolume(6).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetDeviceVolume(int volume) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setDeviceVolume(3); + + // Verify placeholder state and listener calls. + assertThat(player.getDeviceVolume()).isEqualTo(3); + verify(listener).onDeviceVolumeChanged(3, /* muted= */ false); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getDeviceVolume()).isEqualTo(6); + verify(listener).onDeviceVolumeChanged(6, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void setDeviceVolume_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_DEVICE_VOLUME) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetDeviceVolume(int volume) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setDeviceVolume(3); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void increaseDeviceVolume_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setDeviceVolume(3) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = state.buildUpon().setDeviceVolume(6).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleIncreaseDeviceVolume() { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.increaseDeviceVolume(); + + assertThat(player.getDeviceVolume()).isEqualTo(6); + verify(listener).onDeviceVolumeChanged(6, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void increaseDeviceVolume_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setDeviceVolume(3) + .build(); + // Set a new volume to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setDeviceVolume(6).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleIncreaseDeviceVolume() { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.increaseDeviceVolume(); + + // Verify placeholder state and listener calls. + assertThat(player.getDeviceVolume()).isEqualTo(4); + verify(listener).onDeviceVolumeChanged(4, /* muted= */ false); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getDeviceVolume()).isEqualTo(6); + verify(listener).onDeviceVolumeChanged(6, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void increaseDeviceVolume_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_ADJUST_DEVICE_VOLUME) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleIncreaseDeviceVolume() { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.increaseDeviceVolume(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void decreaseDeviceVolume_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setDeviceVolume(3) + .build(); + // Set a different one to the one requested to ensure the updated state is used. + State updatedState = state.buildUpon().setDeviceVolume(1).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleDecreaseDeviceVolume() { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.decreaseDeviceVolume(); + + assertThat(player.getDeviceVolume()).isEqualTo(1); + verify(listener).onDeviceVolumeChanged(1, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void decreaseDeviceVolume_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setDeviceVolume(3) + .build(); + // Set a new volume to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setDeviceVolume(1).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleDecreaseDeviceVolume() { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.decreaseDeviceVolume(); + + // Verify placeholder state and listener calls. + assertThat(player.getDeviceVolume()).isEqualTo(2); + verify(listener).onDeviceVolumeChanged(2, /* muted= */ false); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getDeviceVolume()).isEqualTo(1); + verify(listener).onDeviceVolumeChanged(1, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void decreaseDeviceVolume_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_ADJUST_DEVICE_VOLUME) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleDecreaseDeviceVolume() { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.decreaseDeviceVolume(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setDeviceMuted_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + // Also change the volume to ensure the updated state is used. + State updatedState = state.buildUpon().setIsDeviceMuted(true).setDeviceVolume(6).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetDeviceMuted(boolean muted) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setDeviceMuted(true); + + assertThat(player.isDeviceMuted()).isTrue(); + assertThat(player.getDeviceVolume()).isEqualTo(6); + verify(listener).onDeviceVolumeChanged(6, /* muted= */ true); + verifyNoMoreInteractions(listener); + } + + @Test + public void setDeviceMuted_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + // Always return the same state to revert the muted change. This allows to see a + // difference between the placeholder and new state. + return state; + } + + @Override + protected ListenableFuture handleSetDeviceMuted(boolean muted) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setDeviceMuted(true); + + // Verify placeholder state and listener calls. + assertThat(player.isDeviceMuted()).isTrue(); + verify(listener).onDeviceVolumeChanged(0, /* muted= */ true); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.isDeviceMuted()).isFalse(); + verify(listener).onDeviceVolumeChanged(0, /* muted= */ false); + verifyNoMoreInteractions(listener); + } + + @Test + public void setDeviceMuted_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_ADJUST_DEVICE_VOLUME) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetDeviceMuted(boolean muted) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setDeviceMuted(true); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setVideoSurface_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setSurfaceSize(Size.ZERO) + .build(); + Size updatedSize = new Size(/* width= */ 300, /* height= */ 200); + State updatedState = state.buildUpon().setSurfaceSize(updatedSize).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetVideoOutput(Object videoOutput) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setVideoSurface(new Surface(new SurfaceTexture(0))); + + assertThat(player.getSurfaceSize()).isEqualTo(updatedSize); + verify(listener).onSurfaceSizeChanged(updatedSize.getWidth(), updatedSize.getHeight()); + verifyNoMoreInteractions(listener); + } + + @Test + public void setVideoSurface_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setSurfaceSize(Size.ZERO) + .build(); + SettableFuture future = SettableFuture.create(); + Size updatedSize = new Size(/* width= */ 300, /* height= */ 200); + State updatedState = state.buildUpon().setSurfaceSize(updatedSize).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetVideoOutput(Object videoOutput) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setVideoSurface(new Surface(new SurfaceTexture(0))); + + // Verify placeholder state and listener calls. + assertThat(player.getSurfaceSize()).isEqualTo(Size.UNKNOWN); + verify(listener) + .onSurfaceSizeChanged(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getSurfaceSize()).isEqualTo(updatedSize); + verify(listener).onSurfaceSizeChanged(updatedSize.getWidth(), updatedSize.getHeight()); + verifyNoMoreInteractions(listener); + } + + @Test + public void setVideoSurface_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_VIDEO_SURFACE) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetVideoOutput(Object videoOutput) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setVideoSurface(new Surface(new SurfaceTexture(0))); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void clearVideoSurface_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setSurfaceSize(new Size(/* width= */ 300, /* height= */ 200)) + .build(); + // Change something else in addition to ensure we actually use the updated state. + State updatedState = + state.buildUpon().setSurfaceSize(Size.ZERO).setRepeatMode(Player.REPEAT_MODE_ONE).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.clearVideoSurface(); + + assertThat(player.getSurfaceSize()).isEqualTo(Size.ZERO); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ONE); + verify(listener).onSurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ONE); + verifyNoMoreInteractions(listener); + } + + @Test + public void clearVideoSurface_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setSurfaceSize(new Size(/* width= */ 300, /* height= */ 200)) + .build(); + // Change something else in addition to ensure we actually use the updated state. + State updatedState = + state.buildUpon().setSurfaceSize(Size.ZERO).setRepeatMode(Player.REPEAT_MODE_ONE).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.clearVideoSurface(); + + // Verify placeholder state and listener calls. + assertThat(player.getSurfaceSize()).isEqualTo(Size.ZERO); + verify(listener).onSurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getSurfaceSize()).isEqualTo(Size.ZERO); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ONE); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ONE); + verifyNoMoreInteractions(listener); + } + + @Test + public void clearVideoSurface_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SET_VIDEO_SURFACE) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.clearVideoSurface(); + + assertThat(callForwarded.get()).isFalse(); + } + private static Object[] getAnyArguments(Method method) { Object[] arguments = new Object[method.getParameterCount()]; Class[] argumentTypes = method.getParameterTypes(); From eb51ad57f96f7e967ff2c396ab488c691f718d54 Mon Sep 17 00:00:00 2001 From: Ian Baker Date: Mon, 12 Dec 2022 11:46:10 +0000 Subject: [PATCH 036/104] Merge pull request #10750 from Stronger197:subrip_utf_16 PiperOrigin-RevId: 492164739 (cherry picked from commit 496cfa420d7dd23fb5913c9ecf7b0b1e04cc65ff) --- .../exoplayer2/util/ParsableByteArray.java | 162 ++++++++-- .../util/ParsableByteArrayTest.java | 298 +++++++++++++++++- .../exoplayer2/text/subrip/SubripDecoder.java | 20 +- .../exoplayer2/text/tx3g/Tx3gDecoder.java | 18 +- .../text/subrip/SubripDecoderTest.java | 28 ++ .../test/assets/media/subrip/typical_utf16be | Bin 0 -> 434 bytes .../test/assets/media/subrip/typical_utf16le | Bin 0 -> 434 bytes 7 files changed, 473 insertions(+), 53 deletions(-) create mode 100644 testdata/src/test/assets/media/subrip/typical_utf16be create mode 100644 testdata/src/test/assets/media/subrip/typical_utf16le diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index 79913d2aa9..29c2aa5153 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -17,6 +17,9 @@ package com.google.android.exoplayer2.util; import androidx.annotation.Nullable; import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableSet; +import com.google.common.primitives.Chars; +import com.google.common.primitives.UnsignedBytes; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Arrays; @@ -27,6 +30,12 @@ import java.util.Arrays; */ public final class ParsableByteArray { + private static final char[] CR_AND_LF = {'\r', '\n'}; + private static final char[] LF = {'\n'}; + private static final ImmutableSet SUPPORTED_CHARSETS_FOR_READLINE = + ImmutableSet.of( + Charsets.US_ASCII, Charsets.UTF_8, Charsets.UTF_16, Charsets.UTF_16BE, Charsets.UTF_16LE); + private byte[] data; private int position; // TODO(internal b/147657250): Enforce this limit on all read methods. @@ -489,45 +498,47 @@ public final class ParsableByteArray { } /** - * Reads a line of text. + * Reads a line of text in UTF-8. * - *

    A line is considered to be terminated by any one of a carriage return ('\r'), a line feed - * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The UTF-8 charset is - * used. This method discards leading UTF-8 byte order marks, if present. - * - * @return The line not including any line-termination characters, or null if the end of the data - * has already been reached. + *

    Equivalent to passing {@link Charsets#UTF_8} to {@link #readLine(Charset)}. */ @Nullable public String readLine() { + return readLine(Charsets.UTF_8); + } + + /** + * Reads a line of text in {@code charset}. + * + *

    A line is considered to be terminated by any one of a carriage return ('\r'), a line feed + * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). This method discards + * leading UTF byte order marks (BOM), if present. + * + *

    The {@linkplain #getPosition() position} is advanced to start of the next line (i.e. any + * line terminators are skipped). + * + * @param charset The charset used to interpret the bytes as a {@link String}. + * @return The line not including any line-termination characters, or null if the end of the data + * has already been reached. + * @throws IllegalArgumentException if charset is not supported. Only US_ASCII, UTF-8, UTF-16, + * UTF-16BE, and UTF-16LE are supported. + */ + @Nullable + public String readLine(Charset charset) { + Assertions.checkArgument( + SUPPORTED_CHARSETS_FOR_READLINE.contains(charset), "Unsupported charset: " + charset); if (bytesLeft() == 0) { return null; } - int lineLimit = position; - while (lineLimit < limit && !Util.isLinebreak(data[lineLimit])) { - lineLimit++; + if (!charset.equals(Charsets.US_ASCII)) { + readUtfCharsetFromBom(); // Skip BOM if present } - if (lineLimit - position >= 3 - && data[position] == (byte) 0xEF - && data[position + 1] == (byte) 0xBB - && data[position + 2] == (byte) 0xBF) { - // There's a UTF-8 byte order mark at the start of the line. Discard it. - position += 3; - } - String line = Util.fromUtf8Bytes(data, position, lineLimit - position); - position = lineLimit; + int lineLimit = findNextLineTerminator(charset); + String line = readString(lineLimit - position, charset); if (position == limit) { return line; } - if (data[position] == '\r') { - position++; - if (position == limit) { - return line; - } - } - if (data[position] == '\n') { - position++; - } + skipLineTerminator(charset); return line; } @@ -565,4 +576,99 @@ public final class ParsableByteArray { position += length; return value; } + + /** + * Reads a UTF byte order mark (BOM) and returns the UTF {@link Charset} it represents. Returns + * {@code null} without advancing {@link #getPosition() position} if no BOM is found. + */ + @Nullable + public Charset readUtfCharsetFromBom() { + if (bytesLeft() >= 3 + && data[position] == (byte) 0xEF + && data[position + 1] == (byte) 0xBB + && data[position + 2] == (byte) 0xBF) { + position += 3; + return Charsets.UTF_8; + } else if (bytesLeft() >= 2) { + if (data[position] == (byte) 0xFE && data[position + 1] == (byte) 0xFF) { + position += 2; + return Charsets.UTF_16BE; + } else if (data[position] == (byte) 0xFF && data[position + 1] == (byte) 0xFE) { + position += 2; + return Charsets.UTF_16LE; + } + } + return null; + } + + /** + * Returns the index of the next occurrence of '\n' or '\r', or {@link #limit} if none is found. + */ + private int findNextLineTerminator(Charset charset) { + int stride; + if (charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) { + stride = 1; + } else if (charset.equals(Charsets.UTF_16) + || charset.equals(Charsets.UTF_16LE) + || charset.equals(Charsets.UTF_16BE)) { + stride = 2; + } else { + throw new IllegalArgumentException("Unsupported charset: " + charset); + } + for (int i = position; i < limit - (stride - 1); i += stride) { + if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) + && Util.isLinebreak(data[i])) { + return i; + } else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE)) + && data[i] == 0x00 + && Util.isLinebreak(data[i + 1])) { + return i; + } else if (charset.equals(Charsets.UTF_16LE) + && data[i + 1] == 0x00 + && Util.isLinebreak(data[i])) { + return i; + } + } + return limit; + } + + private void skipLineTerminator(Charset charset) { + if (readCharacterIfInList(charset, CR_AND_LF) == '\r') { + readCharacterIfInList(charset, LF); + } + } + + /** + * Peeks at the character at {@link #position} (as decoded by {@code charset}), returns it and + * advances {@link #position} past it if it's in {@code chars}, otherwise returns {@code 0} + * without advancing {@link #position}. Returns {@code 0} if {@link #bytesLeft()} doesn't allow + * reading a whole character in {@code charset}. + * + *

    Only supports characters in {@code chars} that occupy a single code unit (i.e. one byte for + * UTF-8 and two bytes for UTF-16). + */ + private char readCharacterIfInList(Charset charset, char[] chars) { + char character; + int characterSize; + if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) && bytesLeft() >= 1) { + character = Chars.checkedCast(UnsignedBytes.toInt(data[position])); + characterSize = 1; + } else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE)) + && bytesLeft() >= 2) { + character = Chars.fromBytes(data[position], data[position + 1]); + characterSize = 2; + } else if (charset.equals(Charsets.UTF_16LE) && bytesLeft() >= 2) { + character = Chars.fromBytes(data[position + 1], data[position]); + characterSize = 2; + } else { + return 0; + } + + if (Chars.contains(chars, character)) { + position += characterSize; + return Chars.checkedCast(character); + } else { + return 0; + } + } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java index fbf0368c9f..767aeeaf98 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java @@ -15,11 +15,13 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.Charset.forName; import static org.junit.Assert.fail; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.base.Charsets; import com.google.common.primitives.Bytes; import java.nio.ByteBuffer; import java.util.Arrays; @@ -548,48 +550,324 @@ public final class ParsableByteArrayTest { } @Test - public void readSingleLineWithoutEndingTrail() { - byte[] bytes = new byte[] {'f', 'o', 'o'}; + public void readSingleLineWithoutEndingTrail_ascii() { + byte[] bytes = "foo".getBytes(Charsets.US_ASCII); ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(3); + assertThat(parser.readLine(Charsets.US_ASCII)).isNull(); + } + + @Test + public void readSingleLineWithEndingLf_ascii() { + byte[] bytes = "foo\n".getBytes(Charsets.US_ASCII); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(4); + assertThat(parser.readLine(Charsets.US_ASCII)).isNull(); + } + + @Test + public void readTwoLinesWithCrFollowedByLf_ascii() { + byte[] bytes = "foo\r\nbar".getBytes(Charsets.US_ASCII); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(5); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.US_ASCII)).isNull(); + } + + @Test + public void readThreeLinesWithEmptyLine_ascii() { + byte[] bytes = "foo\r\n\rbar".getBytes(Charsets.US_ASCII); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(5); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(6); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(9); + assertThat(parser.readLine(Charsets.US_ASCII)).isNull(); + } + + @Test + public void readFourLinesWithLfFollowedByCr_ascii() { + byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.US_ASCII); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(4); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(5); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(6); + assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(11); + assertThat(parser.readLine(Charsets.US_ASCII)).isNull(); + } + + @Test + public void readSingleLineWithoutEndingTrail_utf8() { + byte[] bytes = "foo".getBytes(Charsets.UTF_8); + ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readLine()).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(3); assertThat(parser.readLine()).isNull(); } @Test - public void readSingleLineWithEndingLf() { - byte[] bytes = new byte[] {'f', 'o', 'o', '\n'}; + public void readSingleLineWithEndingLf_utf8() { + byte[] bytes = "foo\n".getBytes(Charsets.UTF_8); ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readLine()).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readLine()).isNull(); } @Test - public void readTwoLinesWithCrFollowedByLf() { - byte[] bytes = new byte[] {'f', 'o', 'o', '\r', '\n', 'b', 'a', 'r'}; + public void readTwoLinesWithCrFollowedByLf_utf8() { + byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_8); ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readLine()).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(5); assertThat(parser.readLine()).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readLine()).isNull(); } @Test - public void readThreeLinesWithEmptyLine() { - byte[] bytes = new byte[] {'f', 'o', 'o', '\r', '\n', '\r', 'b', 'a', 'r'}; + public void readThreeLinesWithEmptyLineAndLeadingBom_utf8() { + byte[] bytes = + Bytes.concat(createByteArray(0xEF, 0xBB, 0xBF), "foo\r\n\rbar".getBytes(Charsets.UTF_8)); ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readLine()).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readLine()).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(9); assertThat(parser.readLine()).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(12); assertThat(parser.readLine()).isNull(); } @Test - public void readFourLinesWithLfFollowedByCr() { - byte[] bytes = new byte[] {'f', 'o', 'o', '\n', '\r', '\r', 'b', 'a', 'r', '\r', '\n'}; + public void readFourLinesWithLfFollowedByCr_utf8() { + byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_8); ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readLine()).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readLine()).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(5); assertThat(parser.readLine()).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(6); assertThat(parser.readLine()).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(11); assertThat(parser.readLine()).isNull(); } + + @Test + public void readSingleLineWithoutEndingTrail_utf16() { + // Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We + // explicitly test with a BOM elsewhere. + byte[] bytes = "foo".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(6); + assertThat(parser.readLine(Charsets.UTF_16)).isNull(); + } + + @Test + public void readSingleLineWithEndingLf_utf16() { + // Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We + // explicitly test with a BOM elsewhere. + byte[] bytes = "foo\n".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16)).isNull(); + } + + @Test + public void readTwoLinesWithCrFollowedByLf_utf16() { + // Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We + // explicitly test with a BOM elsewhere. + byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(16); + assertThat(parser.readLine(Charsets.UTF_16)).isNull(); + } + + @Test + public void readThreeLinesWithEmptyLineAndLeadingBom_utf16() { + // getBytes(UTF_16) always adds the leading BOM. + byte[] bytes = "foo\r\n\rbar".getBytes(Charsets.UTF_16); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(14); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(20); + assertThat(parser.readLine(Charsets.UTF_16)).isNull(); + } + + @Test + public void readFourLinesWithLfFollowedByCr_utf16() { + // Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We + // explicitly test with a BOM elsewhere. + byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(22); + assertThat(parser.readLine(Charsets.UTF_16)).isNull(); + } + + @Test + public void readSingleLineWithoutEndingTrail_utf16be() { + byte[] bytes = "foo".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(6); + assertThat(parser.readLine(Charsets.UTF_16BE)).isNull(); + } + + @Test + public void readSingleLineWithEndingLf_utf16be() { + byte[] bytes = "foo\n".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16BE)).isNull(); + } + + @Test + public void readTwoLinesWithCrFollowedByLf_utf16be() { + byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(16); + assertThat(parser.readLine(Charsets.UTF_16BE)).isNull(); + } + + @Test + public void readThreeLinesWithEmptyLineAndLeadingBom_utf16be() { + byte[] bytes = + Bytes.concat(createByteArray(0xFE, 0xFF), "foo\r\n\rbar".getBytes(Charsets.UTF_16BE)); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(14); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(20); + assertThat(parser.readLine(Charsets.UTF_16BE)).isNull(); + } + + @Test + public void readFourLinesWithLfFollowedByCr_utf16be() { + byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16BE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(22); + assertThat(parser.readLine(Charsets.UTF_16BE)).isNull(); + } + + @Test + public void readSingleLineWithoutEndingTrail_utf16le() { + byte[] bytes = "foo".getBytes(Charsets.UTF_16LE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(6); + assertThat(parser.readLine(Charsets.UTF_16LE)).isNull(); + } + + @Test + public void readSingleLineWithEndingLf_utf16le() { + byte[] bytes = "foo\n".getBytes(Charsets.UTF_16LE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16LE)).isNull(); + } + + @Test + public void readTwoLinesWithCrFollowedByLf_utf16le() { + byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16LE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(16); + assertThat(parser.readLine(Charsets.UTF_16LE)).isNull(); + } + + @Test + public void readThreeLinesWithEmptyLineAndLeadingBom_utf16le() { + byte[] bytes = + Bytes.concat(createByteArray(0xFF, 0xFE), "foo\r\n\rbar".getBytes(Charsets.UTF_16LE)); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(14); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(20); + assertThat(parser.readLine(Charsets.UTF_16LE)).isNull(); + } + + @Test + public void readFourLinesWithLfFollowedByCr_utf16le() { + byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16LE); + ParsableByteArray parser = new ParsableByteArray(bytes); + + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo"); + assertThat(parser.getPosition()).isEqualTo(8); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(10); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo(""); + assertThat(parser.getPosition()).isEqualTo(12); + assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar"); + assertThat(parser.getPosition()).isEqualTo(22); + assertThat(parser.readLine(Charsets.UTF_16LE)).isNull(); + } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index f88574dcd5..0c7883b0a5 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -26,6 +26,8 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.common.base.Charsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,9 +76,10 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { ArrayList cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(data, length); + Charset charset = detectUtfCharset(subripData); @Nullable String currentLine; - while ((currentLine = subripData.readLine()) != null) { + while ((currentLine = subripData.readLine(charset)) != null) { if (currentLine.length() == 0) { // Skip blank lines. continue; @@ -91,7 +94,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } // Read and parse the timing line. - currentLine = subripData.readLine(); + currentLine = subripData.readLine(charset); if (currentLine == null) { Log.w(TAG, "Unexpected end"); break; @@ -109,13 +112,13 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { // Read and parse the text and tags. textBuilder.setLength(0); tags.clear(); - currentLine = subripData.readLine(); + currentLine = subripData.readLine(charset); while (!TextUtils.isEmpty(currentLine)) { if (textBuilder.length() > 0) { textBuilder.append("
    "); } textBuilder.append(processLine(currentLine, tags)); - currentLine = subripData.readLine(); + currentLine = subripData.readLine(charset); } Spanned text = Html.fromHtml(textBuilder.toString()); @@ -138,6 +141,15 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { return new SubripSubtitle(cuesArray, cueTimesUsArray); } + /** + * Determine UTF encoding of the byte array from a byte order mark (BOM), defaulting to UTF-8 if + * no BOM is found. + */ + private Charset detectUtfCharset(ParsableByteArray data) { + @Nullable Charset charset = data.readUtfCharsetFromBom(); + return charset != null ? charset : Charsets.UTF_8; + } + /** * Trims and removes tags from the given line. The removed tags are added to {@code tags}. * diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index e1757b711b..eab76a929d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -26,6 +26,7 @@ import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.text.style.UnderlineSpan; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; @@ -35,6 +36,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Charsets; +import java.nio.charset.Charset; import java.util.List; /** @@ -46,16 +48,12 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { private static final String TAG = "Tx3gDecoder"; - private static final char BOM_UTF16_BE = '\uFEFF'; - private static final char BOM_UTF16_LE = '\uFFFE'; - private static final int TYPE_STYL = 0x7374796c; private static final int TYPE_TBOX = 0x74626f78; private static final String TX3G_SERIF = "Serif"; private static final int SIZE_ATOM_HEADER = 8; private static final int SIZE_SHORT = 2; - private static final int SIZE_BOM_UTF16 = 2; private static final int SIZE_STYLE_RECORD = 12; private static final int FONT_FACE_BOLD = 0x0001; @@ -171,13 +169,11 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { if (textLength == 0) { return ""; } - if (parsableByteArray.bytesLeft() >= SIZE_BOM_UTF16) { - char firstChar = parsableByteArray.peekChar(); - if (firstChar == BOM_UTF16_BE || firstChar == BOM_UTF16_LE) { - return parsableByteArray.readString(textLength, Charsets.UTF_16); - } - } - return parsableByteArray.readString(textLength, Charsets.UTF_8); + int textStartPosition = parsableByteArray.getPosition(); + @Nullable Charset charset = parsableByteArray.readUtfCharsetFromBom(); + int bomSize = parsableByteArray.getPosition() - textStartPosition; + return parsableByteArray.readString( + textLength - bomSize, charset != null ? charset : Charsets.UTF_8); } private void applyStyleRecord(ParsableByteArray parsableByteArray, SpannableStringBuilder cueText) diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index c868cc9a70..2707db0ff3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -40,6 +40,8 @@ public final class SubripDecoderTest { private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "media/subrip/typical_negative_timestamps"; private static final String TYPICAL_UNEXPECTED_END = "media/subrip/typical_unexpected_end"; + private static final String TYPICAL_UTF16BE = "media/subrip/typical_utf16be"; + private static final String TYPICAL_UTF16LE = "media/subrip/typical_utf16le"; private static final String TYPICAL_WITH_TAGS = "media/subrip/typical_with_tags"; private static final String TYPICAL_NO_HOURS_AND_MILLIS = "media/subrip/typical_no_hours_and_millis"; @@ -148,6 +150,32 @@ public final class SubripDecoderTest { assertTypicalCue2(subtitle, 2); } + @Test + public void decodeTypicalUtf16LittleEndian() throws IOException { + SubripDecoder decoder = new SubripDecoder(); + byte[] bytes = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16LE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + + assertThat(subtitle.getEventTimeCount()).isEqualTo(6); + assertTypicalCue1(subtitle, 0); + assertTypicalCue2(subtitle, 2); + assertTypicalCue3(subtitle, 4); + } + + @Test + public void decodeTypicalUtf16BigEndian() throws IOException { + SubripDecoder decoder = new SubripDecoder(); + byte[] bytes = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16BE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + + assertThat(subtitle.getEventTimeCount()).isEqualTo(6); + assertTypicalCue1(subtitle, 0); + assertTypicalCue2(subtitle, 2); + assertTypicalCue3(subtitle, 4); + } + @Test public void decodeCueWithTag() throws IOException { SubripDecoder decoder = new SubripDecoder(); diff --git a/testdata/src/test/assets/media/subrip/typical_utf16be b/testdata/src/test/assets/media/subrip/typical_utf16be new file mode 100644 index 0000000000000000000000000000000000000000..9531c268087bec207cf8b766bc60ef01c13b354a GIT binary patch literal 434 zcmaKoYYM_J5QOJ$fhL z8D_R1{Qq)-*}-tqO|8x&-1|vHs%KDIh3R-#L%;p$w?V&&oGVf1wXJ&kW5gQ71~ Date: Thu, 1 Dec 2022 14:48:29 +0000 Subject: [PATCH 037/104] Split SubripDecoder and ParsableByteArray tests In some cases we split a test method, and in other cases we just add line breaks to make the separation between arrange/act/assert more clear. PiperOrigin-RevId: 492182769 (cherry picked from commit 02fa8aa784b518f0ffad5c44da6eec1a94def4ee) --- .../util/ParsableByteArrayTest.java | 85 ++++++++++++++----- .../text/subrip/SubripDecoderTest.java | 17 ++-- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java index 767aeeaf98..300c7a0dc5 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java @@ -332,6 +332,7 @@ public final class ParsableByteArrayTest { public void readLittleEndianLong() { ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF}); + assertThat(byteArray.readLittleEndianLong()).isEqualTo(0xFF00000000000001L); assertThat(byteArray.getPosition()).isEqualTo(8); } @@ -339,6 +340,7 @@ public final class ParsableByteArrayTest { @Test public void readLittleEndianUnsignedInt() { ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x10, 0x00, 0x00, (byte) 0xFF}); + assertThat(byteArray.readLittleEndianUnsignedInt()).isEqualTo(0xFF000010L); assertThat(byteArray.getPosition()).isEqualTo(4); } @@ -346,6 +348,7 @@ public final class ParsableByteArrayTest { @Test public void readLittleEndianInt() { ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, (byte) 0xFF}); + assertThat(byteArray.readLittleEndianInt()).isEqualTo(0xFF000001); assertThat(byteArray.getPosition()).isEqualTo(4); } @@ -354,6 +357,7 @@ public final class ParsableByteArrayTest { public void readLittleEndianUnsignedInt24() { byte[] data = {0x01, 0x02, (byte) 0xFF}; ParsableByteArray byteArray = new ParsableByteArray(data); + assertThat(byteArray.readLittleEndianUnsignedInt24()).isEqualTo(0xFF0201); assertThat(byteArray.getPosition()).isEqualTo(3); } @@ -362,6 +366,7 @@ public final class ParsableByteArrayTest { public void readInt24Positive() { byte[] data = {0x01, 0x02, (byte) 0xFF}; ParsableByteArray byteArray = new ParsableByteArray(data); + assertThat(byteArray.readInt24()).isEqualTo(0x0102FF); assertThat(byteArray.getPosition()).isEqualTo(3); } @@ -370,6 +375,7 @@ public final class ParsableByteArrayTest { public void readInt24Negative() { byte[] data = {(byte) 0xFF, 0x02, (byte) 0x01}; ParsableByteArray byteArray = new ParsableByteArray(data); + assertThat(byteArray.readInt24()).isEqualTo(0xFFFF0201); assertThat(byteArray.getPosition()).isEqualTo(3); } @@ -378,6 +384,7 @@ public final class ParsableByteArrayTest { public void readLittleEndianUnsignedShort() { ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF}); + assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF01); assertThat(byteArray.getPosition()).isEqualTo(2); assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF02); @@ -388,6 +395,7 @@ public final class ParsableByteArrayTest { public void readLittleEndianShort() { ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF}); + assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF01); assertThat(byteArray.getPosition()).isEqualTo(2); assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF02); @@ -422,6 +430,7 @@ public final class ParsableByteArrayTest { (byte) 0x20, }; ParsableByteArray byteArray = new ParsableByteArray(data); + assertThat(byteArray.readString(data.length)).isEqualTo("ä ö ® π √ ± 谢 "); assertThat(byteArray.getPosition()).isEqualTo(data.length); } @@ -430,6 +439,7 @@ public final class ParsableByteArrayTest { public void readAsciiString() { byte[] data = new byte[] {'t', 'e', 's', 't'}; ParsableByteArray testArray = new ParsableByteArray(data); + assertThat(testArray.readString(data.length, forName("US-ASCII"))).isEqualTo("test"); assertThat(testArray.getPosition()).isEqualTo(data.length); } @@ -438,6 +448,7 @@ public final class ParsableByteArrayTest { public void readStringOutOfBoundsDoesNotMovePosition() { byte[] data = {(byte) 0xC3, (byte) 0xA4, (byte) 0x20}; ParsableByteArray byteArray = new ParsableByteArray(data); + try { byteArray.readString(data.length + 1); fail(); @@ -454,17 +465,22 @@ public final class ParsableByteArrayTest { } @Test - public void readNullTerminatedStringWithLengths() { + public void readNullTerminatedStringWithLengths_readLengthsMatchNullPositions() { byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; - // Test with lengths that match NUL byte positions. + ParsableByteArray parser = new ParsableByteArray(bytes); assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readNullTerminatedString(4)).isEqualTo("bar"); assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readNullTerminatedString()).isNull(); - // Test with lengths that do not match NUL byte positions. - parser = new ParsableByteArray(bytes); + } + + @Test + public void readNullTerminatedStringWithLengths_readLengthsDontMatchNullPositions() { + byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; + ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readNullTerminatedString(2)).isEqualTo("fo"); assertThat(parser.getPosition()).isEqualTo(2); assertThat(parser.readNullTerminatedString(2)).isEqualTo("o"); @@ -474,13 +490,23 @@ public final class ParsableByteArrayTest { assertThat(parser.readNullTerminatedString(1)).isEqualTo(""); assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readNullTerminatedString()).isNull(); - // Test with limit at NUL - parser = new ParsableByteArray(bytes, 4); + } + + @Test + public void readNullTerminatedStringWithLengths_limitAtNull() { + byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4); + assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readNullTerminatedString()).isNull(); - // Test with limit before NUL - parser = new ParsableByteArray(bytes, 3); + } + + @Test + public void readNullTerminatedStringWithLengths_limitBeforeNull() { + byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3); + assertThat(parser.readNullTerminatedString(3)).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(3); assertThat(parser.readNullTerminatedString()).isNull(); @@ -489,20 +515,30 @@ public final class ParsableByteArrayTest { @Test public void readNullTerminatedString() { byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; - // Test normal case. ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readNullTerminatedString()).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readNullTerminatedString()).isEqualTo("bar"); assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readNullTerminatedString()).isNull(); - // Test with limit at NUL. - parser = new ParsableByteArray(bytes, 4); + } + + @Test + public void readNullTerminatedString_withLimitAtNull() { + byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4); + assertThat(parser.readNullTerminatedString()).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readNullTerminatedString()).isNull(); - // Test with limit before NUL. - parser = new ParsableByteArray(bytes, 3); + } + + @Test + public void readNullTerminatedString_withLimitBeforeNull() { + byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3); + assertThat(parser.readNullTerminatedString()).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(3); assertThat(parser.readNullTerminatedString()).isNull(); @@ -512,6 +548,7 @@ public final class ParsableByteArrayTest { public void readNullTerminatedStringWithoutEndingNull() { byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r'}; ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readNullTerminatedString()).isEqualTo("foo"); assertThat(parser.readNullTerminatedString()).isEqualTo("bar"); assertThat(parser.readNullTerminatedString()).isNull(); @@ -520,30 +557,40 @@ public final class ParsableByteArrayTest { @Test public void readDelimiterTerminatedString() { byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'}; - // Test normal case. ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar"); assertThat(parser.getPosition()).isEqualTo(8); assertThat(parser.readDelimiterTerminatedString('*')).isNull(); + } + + @Test + public void readDelimiterTerminatedString_limitAtDelimiter() { + byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4); - // Test with limit at delimiter. - parser = new ParsableByteArray(bytes, 4); assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(4); assertThat(parser.readDelimiterTerminatedString('*')).isNull(); - // Test with limit before delimiter. - parser = new ParsableByteArray(bytes, 3); + } + + @Test + public void readDelimiterTerminatedString_limitBeforeDelimiter() { + byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'}; + ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3); + assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo"); assertThat(parser.getPosition()).isEqualTo(3); assertThat(parser.readDelimiterTerminatedString('*')).isNull(); } @Test - public void readDelimiterTerminatedStringWithoutEndingDelimiter() { + public void readDelimiterTerminatedStringW_noDelimiter() { byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r'}; ParsableByteArray parser = new ParsableByteArray(bytes); + assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo"); assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar"); assertThat(parser.readDelimiterTerminatedString('*')).isNull(); diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index 2707db0ff3..828a6b7f7c 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -50,6 +50,7 @@ public final class SubripDecoderTest { public void decodeEmpty() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(0); @@ -60,6 +61,7 @@ public final class SubripDecoderTest { public void decodeTypical() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -74,6 +76,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_WITH_BYTE_ORDER_MARK); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -88,6 +91,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_EXTRA_BLANK_LINE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -103,6 +107,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_TIMECODE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -117,6 +122,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_MISSING_SEQUENCE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -131,6 +137,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_NEGATIVE_TIMESTAMPS); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); @@ -143,6 +150,7 @@ public final class SubripDecoderTest { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UNEXPECTED_END); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -155,6 +163,7 @@ public final class SubripDecoderTest { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16LE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -168,6 +177,7 @@ public final class SubripDecoderTest { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_UTF16BE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -181,23 +191,19 @@ public final class SubripDecoderTest { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_WITH_TAGS); + Subtitle 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."); - assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) .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."); - 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} @@ -215,6 +221,7 @@ public final class SubripDecoderTest { byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), TYPICAL_NO_HOURS_AND_MILLIS); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); From d0691aad6d42432a8141478acd2927a4754d58b4 Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Fri, 2 Dec 2022 13:14:33 +0000 Subject: [PATCH 038/104] Removed ExoPlayer specific states from SimpleBasePlayer PiperOrigin-RevId: 492443147 (cherry picked from commit 08f6fe172732f8a3327746f249888368c39265bc) --- .../android/exoplayer2/SimpleBasePlayer.java | 50 ------------------- .../exoplayer2/SimpleBasePlayerTest.java | 19 ++----- 2 files changed, 5 insertions(+), 64 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index c5c9a10dc5..3a9c2943c0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -121,8 +121,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { private DeviceInfo deviceInfo; private int deviceVolume; private boolean isDeviceMuted; - private int audioSessionId; - private boolean skipSilenceEnabled; private Size surfaceSize; private boolean newlyRenderedFirstFrame; private Metadata timedMetadata; @@ -167,8 +165,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { deviceInfo = DeviceInfo.UNKNOWN; deviceVolume = 0; isDeviceMuted = false; - audioSessionId = C.AUDIO_SESSION_ID_UNSET; - skipSilenceEnabled = false; surfaceSize = Size.UNKNOWN; newlyRenderedFirstFrame = false; timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET); @@ -213,8 +209,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.deviceInfo = state.deviceInfo; this.deviceVolume = state.deviceVolume; this.isDeviceMuted = state.isDeviceMuted; - this.audioSessionId = state.audioSessionId; - this.skipSilenceEnabled = state.skipSilenceEnabled; this.surfaceSize = state.surfaceSize; this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame; this.timedMetadata = state.timedMetadata; @@ -500,30 +494,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { return this; } - /** - * Sets the audio session id. - * - * @param audioSessionId The audio session id. - * @return This builder. - */ - @CanIgnoreReturnValue - public Builder setAudioSessionId(int audioSessionId) { - this.audioSessionId = audioSessionId; - return this; - } - - /** - * Sets whether skipping silences in the audio stream is enabled. - * - * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled. - * @return This builder. - */ - @CanIgnoreReturnValue - public Builder setSkipSilenceEnabled(boolean skipSilenceEnabled) { - this.skipSilenceEnabled = skipSilenceEnabled; - return this; - } - /** * Sets the size of the surface onto which the video is being rendered. * @@ -854,10 +824,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final int deviceVolume; /** Whether the device is muted. */ public final boolean isDeviceMuted; - /** The audio session id. */ - public final int audioSessionId; - /** Whether skipping silences in the audio stream is enabled. */ - public final boolean skipSilenceEnabled; /** The size of the surface onto which the video is being rendered. */ public final Size surfaceSize; /** @@ -998,8 +964,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.deviceInfo = builder.deviceInfo; this.deviceVolume = builder.deviceVolume; this.isDeviceMuted = builder.isDeviceMuted; - this.audioSessionId = builder.audioSessionId; - this.skipSilenceEnabled = builder.skipSilenceEnabled; this.surfaceSize = builder.surfaceSize; this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame; this.timedMetadata = builder.timedMetadata; @@ -1055,8 +1019,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { && deviceInfo.equals(state.deviceInfo) && deviceVolume == state.deviceVolume && isDeviceMuted == state.isDeviceMuted - && audioSessionId == state.audioSessionId - && skipSilenceEnabled == state.skipSilenceEnabled && surfaceSize.equals(state.surfaceSize) && newlyRenderedFirstFrame == state.newlyRenderedFirstFrame && timedMetadata.equals(state.timedMetadata) @@ -1101,8 +1063,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { result = 31 * result + deviceInfo.hashCode(); result = 31 * result + deviceVolume; result = 31 * result + (isDeviceMuted ? 1 : 0); - result = 31 * result + audioSessionId; - result = 31 * result + (skipSilenceEnabled ? 1 : 0); result = 31 * result + surfaceSize.hashCode(); result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0); result = 31 * result + timedMetadata.hashCode(); @@ -3009,11 +2969,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newState.playbackParameters)); } - if (previousState.skipSilenceEnabled != newState.skipSilenceEnabled) { - listeners.queueEvent( - Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, - listener -> listener.onSkipSilenceEnabledChanged(newState.skipSilenceEnabled)); - } if (previousState.repeatMode != newState.repeatMode) { listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, @@ -3060,11 +3015,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { Player.EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(newState.playlistMetadata)); } - if (previousState.audioSessionId != newState.audioSessionId) { - listeners.queueEvent( - Player.EVENT_AUDIO_SESSION_ID, - listener -> listener.onAudioSessionIdChanged(newState.audioSessionId)); - } if (newState.newlyRenderedFirstFrame) { listeners.queueEvent(Player.EVENT_RENDERED_FIRST_FRAME, Listener::onRenderedFirstFrame); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index ee56a1705b..ce94373e62 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -114,8 +114,6 @@ public class SimpleBasePlayerTest { new DeviceInfo( DeviceInfo.PLAYBACK_TYPE_LOCAL, /* minVolume= */ 3, /* maxVolume= */ 7)) .setIsDeviceMuted(true) - .setAudioSessionId(78) - .setSkipSilenceEnabled(true) .setSurfaceSize(new Size(480, 360)) .setNewlyRenderedFirstFrame(true) .setTimedMetadata(new Metadata()) @@ -276,8 +274,6 @@ public class SimpleBasePlayerTest { .setDeviceInfo(deviceInfo) .setDeviceVolume(5) .setIsDeviceMuted(true) - .setAudioSessionId(78) - .setSkipSilenceEnabled(true) .setSurfaceSize(surfaceSize) .setNewlyRenderedFirstFrame(true) .setTimedMetadata(timedMetadata) @@ -318,8 +314,6 @@ public class SimpleBasePlayerTest { assertThat(state.deviceInfo).isEqualTo(deviceInfo); assertThat(state.deviceVolume).isEqualTo(5); assertThat(state.isDeviceMuted).isTrue(); - assertThat(state.audioSessionId).isEqualTo(78); - assertThat(state.skipSilenceEnabled).isTrue(); assertThat(state.surfaceSize).isEqualTo(surfaceSize); assertThat(state.newlyRenderedFirstFrame).isTrue(); assertThat(state.timedMetadata).isEqualTo(timedMetadata); @@ -872,8 +866,6 @@ public class SimpleBasePlayerTest { .setDeviceInfo(deviceInfo) .setDeviceVolume(5) .setIsDeviceMuted(true) - .setAudioSessionId(78) - .setSkipSilenceEnabled(true) .setSurfaceSize(surfaceSize) .setPlaylist(playlist) .setPlaylistMetadata(playlistMetadata) @@ -1167,8 +1159,6 @@ public class SimpleBasePlayerTest { .setDeviceInfo(deviceInfo) .setDeviceVolume(5) .setIsDeviceMuted(true) - .setAudioSessionId(78) - .setSkipSilenceEnabled(true) .setSurfaceSize(surfaceSize) .setNewlyRenderedFirstFrame(true) .setTimedMetadata(timedMetadata) @@ -1234,11 +1224,9 @@ public class SimpleBasePlayerTest { verify(listener).onMediaMetadataChanged(mediaMetadata); verify(listener).onTracksChanged(tracks); verify(listener).onPlaylistMetadataChanged(playlistMetadata); - verify(listener).onAudioSessionIdChanged(78); verify(listener).onRenderedFirstFrame(); verify(listener).onMetadata(timedMetadata); verify(listener).onSurfaceSizeChanged(surfaceSize.getWidth(), surfaceSize.getHeight()); - verify(listener).onSkipSilenceEnabledChanged(true); verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); verify(listener) .onPositionDiscontinuity( @@ -1291,9 +1279,7 @@ public class SimpleBasePlayerTest { Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, - Player.EVENT_AUDIO_SESSION_ID, Player.EVENT_VOLUME_CHANGED, - Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, Player.EVENT_SURFACE_SIZE_CHANGED, Player.EVENT_VIDEO_SIZE_CHANGED, Player.EVENT_RENDERED_FIRST_FRAME, @@ -1308,6 +1294,11 @@ public class SimpleBasePlayerTest { if (method.getName().equals("onSeekProcessed")) { continue; } + if (method.getName().equals("onAudioSessionIdChanged") + || method.getName().equals("onSkipSilenceEnabledChanged")) { + // Skip listeners for ExoPlayer-specific states + continue; + } method.invoke(verify(listener), getAnyArguments(method)); } } From e342b70e93b0ca6135f94e66b5baf94877f7683f Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Dec 2022 15:29:19 +0000 Subject: [PATCH 039/104] Fix `TextRenderer` exception when a subtitle file contains no cues Discovered while investigating Issue: google/ExoPlayer#10823 Example stack trace with the previous code (I added the index value for debugging): ``` playerFailed [eventTime=44.07, mediaPos=44.01, window=0, period=0, errorCode=ERROR_CODE_FAILED_RUNTIME_CHECK androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:635) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loopOnce(Looper.java:202) at android.os.Looper.loop(Looper.java:291) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: java.lang.IllegalArgumentException: index=-1 at androidx.media3.common.util.Assertions.checkArgument(Assertions.java:55) at androidx.media3.extractor.text.webvtt.WebvttSubtitle.getEventTime(WebvttSubtitle.java:62) at androidx.media3.extractor.text.SubtitleOutputBuffer.getEventTime(SubtitleOutputBuffer.java:56) at androidx.media3.exoplayer.text.TextRenderer.getCurrentEventTimeUs(TextRenderer.java:435) at androidx.media3.exoplayer.text.TextRenderer.render(TextRenderer.java:268) at androidx.media3.exoplayer.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:1008) at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:509) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loopOnce(Looper.java:202) at android.os.Looper.loop(Looper.java:291) at android.os.HandlerThread.run(HandlerThread.java:67) ] ``` #minor-release PiperOrigin-RevId: 492464180 (cherry picked from commit 5f6fde4d2a90d78a8d58430c88d7dc12849fe163) --- .../java/com/google/android/exoplayer2/text/TextRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 58ead180c9..862463d33b 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 @@ -418,7 +418,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { @SideEffectFree private long getCurrentEventTimeUs(long positionUs) { int nextEventTimeIndex = subtitle.getNextEventTimeIndex(positionUs); - if (nextEventTimeIndex == 0) { + if (nextEventTimeIndex == 0 || subtitle.getEventTimeCount() == 0) { return subtitle.timeUs; } From fec7b1b5752a81d8eea4296a2e7731df8f9e3b3e Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Dec 2022 16:05:02 +0000 Subject: [PATCH 040/104] Fix `ExoPlayerTest` to use `C.TIME_UNSET` instead of `C.POSITION_UNSET` This inconsistency was exposed by an upcoming change to deprecate `POSITION_UNSET` in favour of `INDEX_UNSET` because position is an ambiguous term between 'byte offset' and 'media position', as shown here. PiperOrigin-RevId: 492470241 (cherry picked from commit 2f8cf947c71db6be9459492117fe95b1a8bc7178) --- .../java/com/google/android/exoplayer2/ExoPlayerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 f60857e2a6..d760ea8c0e 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 @@ -2514,7 +2514,7 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET); + assertThat(target.positionMs).isEqualTo(C.TIME_UNSET); } @Test @@ -2536,7 +2536,7 @@ public final class ExoPlayerTest { .build() .start() .blockUntilEnded(TIMEOUT_MS); - assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET); + assertThat(target.positionMs).isEqualTo(C.TIME_UNSET); } @Test @@ -12282,7 +12282,7 @@ public final class ExoPlayerTest { public PositionGrabbingMessageTarget() { mediaItemIndex = C.INDEX_UNSET; - positionMs = C.POSITION_UNSET; + positionMs = C.TIME_UNSET; } @Override From 5fd6ce5e2e275efee5c43d0bab7a72da51b93f68 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 2 Dec 2022 16:24:37 +0000 Subject: [PATCH 041/104] Fix threading of onFallbackApplied callback The callback is currently triggered on the ExoPlayer playback thread instead of the app thread that added the listener. PiperOrigin-RevId: 492474405 (cherry picked from commit f3fc4fb9735a4b67a3740b3796495017eb5c978c) --- .../transformer/FallbackListener.java | 28 +++++++++++------ .../exoplayer2/transformer/Transformer.java | 6 +++- .../transformer/FallbackListenerTest.java | 31 ++++++++++++++++--- .../transformer/VideoEncoderWrapperTest.java | 1 + 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FallbackListener.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FallbackListener.java index 69063d990a..308f2352fb 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FallbackListener.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/FallbackListener.java @@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkState; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Util; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.util.Util; private final MediaItem mediaItem; private final TransformationRequest originalTransformationRequest; private final ListenerSet transformerListeners; + private final HandlerWrapper transformerListenerHandler; private TransformationRequest fallbackTransformationRequest; private int trackCount; @@ -40,16 +42,20 @@ import com.google.android.exoplayer2.util.Util; * Creates a new instance. * * @param mediaItem The {@link MediaItem} to transform. - * @param transformerListeners The {@linkplain Transformer.Listener listeners} to forward events - * to. + * @param transformerListeners The {@linkplain Transformer.Listener listeners} to call {@link + * Transformer.Listener#onFallbackApplied} on. + * @param transformerListenerHandler The {@link HandlerWrapper} to call {@link + * Transformer.Listener#onFallbackApplied} events on. * @param originalTransformationRequest The original {@link TransformationRequest}. */ public FallbackListener( MediaItem mediaItem, ListenerSet transformerListeners, + HandlerWrapper transformerListenerHandler, TransformationRequest originalTransformationRequest) { this.mediaItem = mediaItem; this.transformerListeners = transformerListeners; + this.transformerListenerHandler = transformerListenerHandler; this.originalTransformationRequest = originalTransformationRequest; this.fallbackTransformationRequest = originalTransformationRequest; } @@ -104,15 +110,19 @@ import com.google.android.exoplayer2.util.Util; fallbackRequestBuilder.setEnableRequestSdrToneMapping( transformationRequest.enableRequestSdrToneMapping); } - fallbackTransformationRequest = fallbackRequestBuilder.build(); + TransformationRequest newFallbackTransformationRequest = fallbackRequestBuilder.build(); + fallbackTransformationRequest = newFallbackTransformationRequest; if (trackCount == 0 && !originalTransformationRequest.equals(fallbackTransformationRequest)) { - transformerListeners.queueEvent( - /* eventFlag= */ C.INDEX_UNSET, - listener -> - listener.onFallbackApplied( - mediaItem, originalTransformationRequest, fallbackTransformationRequest)); - transformerListeners.flushEvents(); + transformerListenerHandler.post( + () -> + transformerListeners.sendEvent( + /* eventFlag= */ C.INDEX_UNSET, + listener -> + listener.onFallbackApplied( + mediaItem, + originalTransformationRequest, + newFallbackTransformationRequest))); } } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 0bb0209627..31eee1a9a0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -727,7 +727,11 @@ public final class Transformer { /* asyncErrorListener= */ componentListener); this.muxerWrapper = muxerWrapper; FallbackListener fallbackListener = - new FallbackListener(mediaItem, listeners, transformationRequest); + new FallbackListener( + mediaItem, + listeners, + clock.createHandler(looper, /* callback= */ null), + transformationRequest); exoPlayerAssetLoader.start( mediaItem, muxerWrapper, diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/FallbackListenerTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/FallbackListenerTest.java index 60a26756b3..80aa79b069 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/FallbackListenerTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/FallbackListenerTest.java @@ -26,10 +26,12 @@ import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.MimeTypes; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.shadows.ShadowLooper; /** Unit tests for {@link FallbackListener}. */ @RunWith(AndroidJUnit4.class) @@ -41,7 +43,8 @@ public class FallbackListenerTest { public void onTransformationRequestFinalized_withoutTrackRegistration_throwsException() { TransformationRequest transformationRequest = new TransformationRequest.Builder().build(); FallbackListener fallbackListener = - new FallbackListener(PLACEHOLDER_MEDIA_ITEM, createListenerSet(), transformationRequest); + new FallbackListener( + PLACEHOLDER_MEDIA_ITEM, createListenerSet(), createHandler(), transformationRequest); assertThrows( IllegalStateException.class, @@ -52,10 +55,12 @@ public class FallbackListenerTest { public void onTransformationRequestFinalized_afterTrackRegistration_completesSuccessfully() { TransformationRequest transformationRequest = new TransformationRequest.Builder().build(); FallbackListener fallbackListener = - new FallbackListener(PLACEHOLDER_MEDIA_ITEM, createListenerSet(), transformationRequest); + new FallbackListener( + PLACEHOLDER_MEDIA_ITEM, createListenerSet(), createHandler(), transformationRequest); fallbackListener.registerTrack(); fallbackListener.onTransformationRequestFinalized(transformationRequest); + ShadowLooper.idleMainLooper(); } @Test @@ -66,10 +71,14 @@ public class FallbackListenerTest { Transformer.Listener mockListener = mock(Transformer.Listener.class); FallbackListener fallbackListener = new FallbackListener( - PLACEHOLDER_MEDIA_ITEM, createListenerSet(mockListener), originalRequest); + PLACEHOLDER_MEDIA_ITEM, + createListenerSet(mockListener), + createHandler(), + originalRequest); fallbackListener.registerTrack(); fallbackListener.onTransformationRequestFinalized(unchangedRequest); + ShadowLooper.idleMainLooper(); verify(mockListener, never()).onFallbackApplied(any(), any(), any()); } @@ -83,10 +92,14 @@ public class FallbackListenerTest { Transformer.Listener mockListener = mock(Transformer.Listener.class); FallbackListener fallbackListener = new FallbackListener( - PLACEHOLDER_MEDIA_ITEM, createListenerSet(mockListener), originalRequest); + PLACEHOLDER_MEDIA_ITEM, + createListenerSet(mockListener), + createHandler(), + originalRequest); fallbackListener.registerTrack(); fallbackListener.onTransformationRequestFinalized(audioFallbackRequest); + ShadowLooper.idleMainLooper(); verify(mockListener) .onFallbackApplied(PLACEHOLDER_MEDIA_ITEM, originalRequest, audioFallbackRequest); @@ -109,12 +122,16 @@ public class FallbackListenerTest { Transformer.Listener mockListener = mock(Transformer.Listener.class); FallbackListener fallbackListener = new FallbackListener( - PLACEHOLDER_MEDIA_ITEM, createListenerSet(mockListener), originalRequest); + PLACEHOLDER_MEDIA_ITEM, + createListenerSet(mockListener), + createHandler(), + originalRequest); fallbackListener.registerTrack(); fallbackListener.registerTrack(); fallbackListener.onTransformationRequestFinalized(audioFallbackRequest); fallbackListener.onTransformationRequestFinalized(videoFallbackRequest); + ShadowLooper.idleMainLooper(); verify(mockListener) .onFallbackApplied(PLACEHOLDER_MEDIA_ITEM, originalRequest, mergedFallbackRequest); @@ -130,4 +147,8 @@ public class FallbackListenerTest { private static ListenerSet createListenerSet() { return new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, (listener, flags) -> {}); } + + private static HandlerWrapper createHandler() { + return Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null); + } } diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java index fe17980468..c5a3d46a05 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java @@ -45,6 +45,7 @@ public final class VideoEncoderWrapperTest { new FallbackListener( MediaItem.fromUri(Uri.EMPTY), new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, (listener, flags) -> {}), + Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null), emptyTransformationRequest); private final VideoTranscodingSamplePipeline.EncoderWrapper encoderWrapper = new VideoTranscodingSamplePipeline.EncoderWrapper( From d2d45dbe17557ab4cf130541418cfd796ae8fc08 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 6 Dec 2022 13:22:18 +0000 Subject: [PATCH 042/104] Add javadoc links to README files Fix some other link titles and destinations spotted along the way. #minor-release PiperOrigin-RevId: 493276172 (cherry picked from commit c006575d4306f9bfad9c6f27d964fab00a6e1718) --- extensions/av1/README.md | 4 +++- extensions/ffmpeg/README.md | 6 +++--- extensions/flac/README.md | 4 +++- extensions/opus/README.md | 4 +++- library/effect/README.md | 6 ++++++ library/rtsp/README.md | 2 +- library/transformer/README.md | 7 ++----- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/extensions/av1/README.md b/extensions/av1/README.md index f5b3fca1d1..009bb19daf 100644 --- a/extensions/av1/README.md +++ b/extensions/av1/README.md @@ -135,6 +135,8 @@ GL rendering mode has better performance, so should be preferred ## Links -* [Javadoc][] +* [Troubleshooting using decoding extensions][] +* [Javadoc][] +[Troubleshooting using decoding extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index f4d596dc57..aa34f16ce8 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -127,8 +127,8 @@ To try out playback using the module in the [demo application][], see ## Links -* [Troubleshooting using extensions][] -* [Javadoc][] +* [Troubleshooting using decoding extensions][] +* [Javadoc][] -[Troubleshooting using extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback +[Troubleshooting using decoding extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/flac/README.md b/extensions/flac/README.md index 0614678f92..6f589ca26c 100644 --- a/extensions/flac/README.md +++ b/extensions/flac/README.md @@ -106,6 +106,8 @@ To try out playback using the module in the [demo application][], see ## Links -* [Javadoc][] +* [Troubleshooting using decoding extensions][] +* [Javadoc][] +[Troubleshooting using decoding extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/extensions/opus/README.md b/extensions/opus/README.md index e669344839..84fd92e34e 100644 --- a/extensions/opus/README.md +++ b/extensions/opus/README.md @@ -110,6 +110,8 @@ To try out playback using the module in the [demo application][], see ## Links -* [Javadoc][] +* [Troubleshooting using decoding extensions][] +* [Javadoc][] +[Troubleshooting using decoding extensions]: https://exoplayer.dev/troubleshooting.html#how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/effect/README.md b/library/effect/README.md index 82094a97b3..23a50eb0fa 100644 --- a/library/effect/README.md +++ b/library/effect/README.md @@ -17,3 +17,9 @@ Alternatively, you can clone this GitHub project and depend on the module locally. Instructions for doing this can be found in the [top level README][]. [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md + +## Links + +* [Javadoc][] + +[Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/rtsp/README.md b/library/rtsp/README.md index c0a94bff5e..f084c4879f 100644 --- a/library/rtsp/README.md +++ b/library/rtsp/README.md @@ -33,5 +33,5 @@ instances and pass them directly to the player. * [Developer Guide][] * [Javadoc][] -[Developer Guide]: https://exoplayer.dev/dash.html +[Developer Guide]: https://exoplayer.dev/rtsp.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html diff --git a/library/transformer/README.md b/library/transformer/README.md index b9dc677531..7e57d1d165 100644 --- a/library/transformer/README.md +++ b/library/transformer/README.md @@ -18,13 +18,10 @@ locally. Instructions for doing this can be found in the [top level README][]. [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md -## Using the module - -Use of the Transformer module is documented in the -[developer guide](https://exoplayer.dev/transforming-media.html). - ## Links +* [Developer Guide][] * [Javadoc][] +[Developer Guide]: https://exoplayer.dev/transforming-media.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html From a65d9e956d6206e039c5c10864113ba7f281e7ed Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 7 Dec 2022 10:19:17 +0000 Subject: [PATCH 043/104] Support release in SimpleBasePlayer This adds support for the release handling. To align with the established behavior in ExoPlayer, the player can only call listeners from within the release methods (and not afterwards) and automatically enforces an IDLE state (without listener call) in case getters of the player are used after release. PiperOrigin-RevId: 493543958 (cherry picked from commit 3a66c28d4f0fc6bbd7af3f5846a5645aa3ccf778) --- .../android/exoplayer2/SimpleBasePlayer.java | 79 ++++++--- .../exoplayer2/SimpleBasePlayerTest.java | 151 ++++++++++++++++++ 2 files changed, 208 insertions(+), 22 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 3a9c2943c0..66a3d8ced9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -1940,6 +1940,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { private final Timeline.Period period; private @MonotonicNonNull State state; + private boolean released; /** * Creates the base class. @@ -2002,7 +2003,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_PLAY_PAUSE)) { + if (!shouldHandleCommand(Player.COMMAND_PLAY_PAUSE)) { return; } updateStateForPendingOperation( @@ -2056,7 +2057,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_PREPARE)) { + if (!shouldHandleCommand(Player.COMMAND_PREPARE)) { return; } updateStateForPendingOperation( @@ -2094,7 +2095,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_REPEAT_MODE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_REPEAT_MODE)) { return; } updateStateForPendingOperation( @@ -2114,7 +2115,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_SHUFFLE_MODE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_SHUFFLE_MODE)) { return; } updateStateForPendingOperation( @@ -2170,7 +2171,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_SPEED_AND_PITCH)) { + if (!shouldHandleCommand(Player.COMMAND_SET_SPEED_AND_PITCH)) { return; } updateStateForPendingOperation( @@ -2190,7 +2191,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_STOP)) { + if (!shouldHandleCommand(Player.COMMAND_STOP)) { return; } updateStateForPendingOperation( @@ -2215,8 +2216,25 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void release() { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (released) { // TODO(b/261158047): Replace by !shouldHandleCommand(Player.COMMAND_RELEASE) + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleRelease(), /* placeholderStateSupplier= */ () -> state); + released = true; + listeners.release(); + // Enforce some final state values in case getters are called after release. + this.state = + this.state + .buildUpon() + .setPlaybackState(Player.STATE_IDLE) + .setTotalBufferedDurationMs(PositionSupplier.ZERO) + .setContentBufferedPositionMs(state.contentPositionMsSupplier) + .setAdBufferedPositionMs(state.adPositionMsSupplier) + .build(); } @Override @@ -2236,7 +2254,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { + if (!shouldHandleCommand(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { return; } updateStateForPendingOperation( @@ -2262,7 +2280,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_MEDIA_ITEMS_METADATA)) { + if (!shouldHandleCommand(Player.COMMAND_SET_MEDIA_ITEMS_METADATA)) { return; } updateStateForPendingOperation( @@ -2363,7 +2381,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VOLUME)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VOLUME)) { return; } updateStateForPendingOperation( @@ -2382,7 +2400,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surface == null) { @@ -2400,7 +2418,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surfaceHolder == null) { @@ -2418,7 +2436,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surfaceView == null) { @@ -2439,7 +2457,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (textureView == null) { @@ -2487,7 +2505,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_VIDEO_SURFACE)) { + if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } updateStateForPendingOperation( @@ -2536,7 +2554,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_SET_DEVICE_VOLUME)) { + if (!shouldHandleCommand(Player.COMMAND_SET_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( @@ -2549,7 +2567,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( @@ -2563,7 +2581,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( @@ -2577,7 +2595,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; - if (!state.availableCommands.contains(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { + if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( @@ -2596,7 +2614,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ protected final void invalidateState() { verifyApplicationThreadAndInitState(); - if (!pendingOperations.isEmpty()) { + if (!pendingOperations.isEmpty() || released) { return; } updateStateAndInformListeners(getState()); @@ -2675,6 +2693,18 @@ public abstract class SimpleBasePlayer extends BasePlayer { throw new IllegalStateException(); } + /** + * Handles calls to {@link Player#release}. + * + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + // TODO(b/261158047): Add that this method will only be called if COMMAND_RELEASE is available. + @ForOverride + protected ListenableFuture handleRelease() { + throw new IllegalStateException(); + } + /** * Handles calls to {@link Player#setRepeatMode}. * @@ -2847,6 +2877,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { throw new IllegalStateException(); } + @RequiresNonNull("state") + private boolean shouldHandleCommand(@Player.Command int commandCode) { + return !released && state.availableCommands.contains(commandCode); + } + @SuppressWarnings("deprecation") // Calling deprecated listener methods. @RequiresNonNull("state") private void updateStateAndInformListeners(State newState) { @@ -3091,7 +3126,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { () -> { castNonNull(state); // Already checked by method @RequiresNonNull pre-condition. pendingOperations.remove(pendingOperation); - if (pendingOperations.isEmpty()) { + if (pendingOperations.isEmpty() && !released) { updateStateAndInformListeners(getState()); } }, diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index ce94373e62..db3c3d16ce 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -57,6 +58,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.shadows.ShadowLooper; @@ -2169,6 +2171,155 @@ public class SimpleBasePlayerTest { assertThat(callForwarded.get()).isFalse(); } + @Test + public void release_immediateHandling_updatesStateInformsListenersAndReturnsIdle() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_READY) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + State updatedState = state.buildUpon().setRepeatMode(Player.REPEAT_MODE_ALL).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleRelease() { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.release(); + + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verify(listener).onEvents(eq(player), any()); + verifyNoMoreInteractions(listener); + } + + @Test + public void release_asyncHandling_returnsIdleAndIgnoredAsyncStateUpdate() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaybackState(Player.STATE_READY) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .build(); + // Additionally set the repeat mode to see a difference between the placeholder and new state. + State updatedState = state.buildUpon().setRepeatMode(Player.REPEAT_MODE_ALL).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRelease() { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.release(); + + // Verify initial change to IDLE without listener call. + assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify no further update happened. + assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_OFF); + verifyNoMoreInteractions(listener); + } + + @Ignore("b/261158047: Ignore test while Player.COMMAND_RELEASE doesn't exist.") + @Test + public void release_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + // TODO(b/261158047): Uncomment once test is no longer ignored. + // .setAvailableCommands( + // new Commands.Builder().addAllCommands().remove(Player.COMMAND_RELEASE).build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleRelease() { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.release(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void release_withSubsequentPlayerAction_ignoresSubsequentAction() { + AtomicBoolean releaseCalled = new AtomicBoolean(); + AtomicBoolean getStateCalledAfterRelease = new AtomicBoolean(); + AtomicBoolean handlePlayWhenReadyCalledAfterRelease = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + if (releaseCalled.get()) { + getStateCalledAfterRelease.set(true); + } + return new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + } + + @Override + protected ListenableFuture handleSetPlayWhenReady(boolean playWhenReady) { + if (releaseCalled.get()) { + handlePlayWhenReadyCalledAfterRelease.set(true); + } + return Futures.immediateVoidFuture(); + } + + @Override + protected ListenableFuture handleRelease() { + return Futures.immediateVoidFuture(); + } + }; + + player.release(); + releaseCalled.set(true); + // Try triggering a regular player action and to invalidate the state manually. + player.setPlayWhenReady(true); + player.invalidateState(); + + assertThat(getStateCalledAfterRelease.get()).isFalse(); + assertThat(handlePlayWhenReadyCalledAfterRelease.get()).isFalse(); + } + @Test public void setRepeatMode_immediateHandling_updatesStateAndInformsListeners() { State state = From a599289bdc9ba576abe1762ac736dd96f0b21782 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 7 Dec 2022 10:22:45 +0000 Subject: [PATCH 044/104] Replace MediaMetadata folderType by isBrowsable The folder type has a mix of information about the item. It shows whether the item is browsable (type != FOLDER_TYPE_NONE) and which Bluetooth folder type to set for legacy session information. It's a lot clearer to split this into a boolean isBrowsable and use the existing mediaType to map back to the bluetooth folder type where required. folderType is not marked as deprecated yet as this would be an API change, which will be done later. PiperOrigin-RevId: 493544589 (cherry picked from commit 9d059352cff0a75f0f9c4e37010bbfa2fc6079fd) --- .../android/exoplayer2/MediaMetadata.java | 131 +++++++++++++++++- .../android/exoplayer2/MediaMetadataTest.java | 57 ++++++++ 2 files changed, 183 insertions(+), 5 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 77bbcb9de8..102f26b00c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -61,6 +61,7 @@ public final class MediaMetadata implements Bundleable { @Nullable private Integer trackNumber; @Nullable private Integer totalTrackCount; @Nullable private @FolderType Integer folderType; + @Nullable private Boolean isBrowsable; @Nullable private Boolean isPlayable; @Nullable private Integer recordingYear; @Nullable private Integer recordingMonth; @@ -97,6 +98,7 @@ public final class MediaMetadata implements Bundleable { this.trackNumber = mediaMetadata.trackNumber; this.totalTrackCount = mediaMetadata.totalTrackCount; this.folderType = mediaMetadata.folderType; + this.isBrowsable = mediaMetadata.isBrowsable; this.isPlayable = mediaMetadata.isPlayable; this.recordingYear = mediaMetadata.recordingYear; this.recordingMonth = mediaMetadata.recordingMonth; @@ -245,13 +247,25 @@ public final class MediaMetadata implements Bundleable { return this; } - /** Sets the {@link FolderType}. */ + /** + * Sets the {@link FolderType}. + * + *

    This method will be deprecated. Use {@link #setIsBrowsable} to indicate if an item is a + * browsable folder and use {@link #setMediaType} to indicate the type of the folder. + */ @CanIgnoreReturnValue public Builder setFolderType(@Nullable @FolderType Integer folderType) { this.folderType = folderType; return this; } + /** Sets whether the media is a browsable folder. */ + @CanIgnoreReturnValue + public Builder setIsBrowsable(@Nullable Boolean isBrowsable) { + this.isBrowsable = isBrowsable; + return this; + } + /** Sets whether the media is playable. */ @CanIgnoreReturnValue public Builder setIsPlayable(@Nullable Boolean isPlayable) { @@ -485,6 +499,9 @@ public final class MediaMetadata implements Bundleable { if (mediaMetadata.folderType != null) { setFolderType(mediaMetadata.folderType); } + if (mediaMetadata.isBrowsable != null) { + setIsBrowsable(mediaMetadata.isBrowsable); + } if (mediaMetadata.isPlayable != null) { setIsPlayable(mediaMetadata.isPlayable); } @@ -867,9 +884,16 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Integer trackNumber; /** Optional total number of tracks. */ @Nullable public final Integer totalTrackCount; - /** Optional {@link FolderType}. */ + /** + * Optional {@link FolderType}. + * + *

    This field will be deprecated. Use {@link #isBrowsable} to indicate if an item is a + * browsable folder and use {@link #mediaType} to indicate the type of the folder. + */ @Nullable public final @FolderType Integer folderType; - /** Optional boolean for media playability. */ + /** Optional boolean to indicate that the media is a browsable folder. */ + @Nullable public final Boolean isBrowsable; + /** Optional boolean to indicate that the media is playable. */ @Nullable public final Boolean isPlayable; /** * @deprecated Use {@link #recordingYear} instead. @@ -932,6 +956,22 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Bundle extras; private MediaMetadata(Builder builder) { + // Handle compatibility for deprecated fields. + @Nullable Boolean isBrowsable = builder.isBrowsable; + @Nullable Integer folderType = builder.folderType; + @Nullable Integer mediaType = builder.mediaType; + if (isBrowsable != null) { + if (!isBrowsable) { + folderType = FOLDER_TYPE_NONE; + } else if (folderType == null || folderType == FOLDER_TYPE_NONE) { + folderType = mediaType != null ? getFolderTypeFromMediaType(mediaType) : FOLDER_TYPE_MIXED; + } + } else if (folderType != null) { + isBrowsable = folderType != FOLDER_TYPE_NONE; + if (isBrowsable && mediaType == null) { + mediaType = getMediaTypeFromFolderType(folderType); + } + } this.title = builder.title; this.artist = builder.artist; this.albumTitle = builder.albumTitle; @@ -946,7 +986,8 @@ public final class MediaMetadata implements Bundleable { this.artworkUri = builder.artworkUri; this.trackNumber = builder.trackNumber; this.totalTrackCount = builder.totalTrackCount; - this.folderType = builder.folderType; + this.folderType = folderType; + this.isBrowsable = isBrowsable; this.isPlayable = builder.isPlayable; this.year = builder.recordingYear; this.recordingYear = builder.recordingYear; @@ -963,7 +1004,7 @@ public final class MediaMetadata implements Bundleable { this.genre = builder.genre; this.compilation = builder.compilation; this.station = builder.station; - this.mediaType = builder.mediaType; + this.mediaType = mediaType; this.extras = builder.extras; } @@ -996,6 +1037,7 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(trackNumber, that.trackNumber) && Util.areEqual(totalTrackCount, that.totalTrackCount) && Util.areEqual(folderType, that.folderType) + && Util.areEqual(isBrowsable, that.isBrowsable) && Util.areEqual(isPlayable, that.isPlayable) && Util.areEqual(recordingYear, that.recordingYear) && Util.areEqual(recordingMonth, that.recordingMonth) @@ -1032,6 +1074,7 @@ public final class MediaMetadata implements Bundleable { trackNumber, totalTrackCount, folderType, + isBrowsable, isPlayable, recordingYear, recordingMonth, @@ -1088,6 +1131,7 @@ public final class MediaMetadata implements Bundleable { FIELD_COMPILATION, FIELD_STATION, FIELD_MEDIA_TYPE, + FIELD_IS_BROWSABLE, FIELD_EXTRAS, }) private @interface FieldNumber {} @@ -1124,6 +1168,7 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_ARTWORK_DATA_TYPE = 29; private static final int FIELD_STATION = 30; private static final int FIELD_MEDIA_TYPE = 31; + private static final int FIELD_IS_BROWSABLE = 32; private static final int FIELD_EXTRAS = 1000; @Override @@ -1160,6 +1205,9 @@ public final class MediaMetadata implements Bundleable { if (folderType != null) { bundle.putInt(keyForField(FIELD_FOLDER_TYPE), folderType); } + if (isBrowsable != null) { + bundle.putBoolean(keyForField(FIELD_IS_BROWSABLE), isBrowsable); + } if (isPlayable != null) { bundle.putBoolean(keyForField(FIELD_IS_PLAYABLE), isPlayable); } @@ -1247,6 +1295,9 @@ public final class MediaMetadata implements Bundleable { if (bundle.containsKey(keyForField(FIELD_FOLDER_TYPE))) { builder.setFolderType(bundle.getInt(keyForField(FIELD_FOLDER_TYPE))); } + if (bundle.containsKey(keyForField(FIELD_IS_BROWSABLE))) { + builder.setIsBrowsable(bundle.getBoolean(keyForField(FIELD_IS_BROWSABLE))); + } if (bundle.containsKey(keyForField(FIELD_IS_PLAYABLE))) { builder.setIsPlayable(bundle.getBoolean(keyForField(FIELD_IS_PLAYABLE))); } @@ -1284,4 +1335,74 @@ public final class MediaMetadata implements Bundleable { private static String keyForField(@FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); } + + private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) { + switch (mediaType) { + case MEDIA_TYPE_ALBUM: + case MEDIA_TYPE_ARTIST: + case MEDIA_TYPE_AUDIO_BOOK: + case MEDIA_TYPE_AUDIO_BOOK_CHAPTER: + case MEDIA_TYPE_FOLDER_MOVIES: + case MEDIA_TYPE_FOLDER_NEWS: + case MEDIA_TYPE_FOLDER_RADIO_STATIONS: + case MEDIA_TYPE_FOLDER_TRAILERS: + case MEDIA_TYPE_FOLDER_VIDEOS: + case MEDIA_TYPE_GENRE: + case MEDIA_TYPE_MOVIE: + case MEDIA_TYPE_MUSIC: + case MEDIA_TYPE_NEWS: + case MEDIA_TYPE_PLAYLIST: + case MEDIA_TYPE_PODCAST: + case MEDIA_TYPE_PODCAST_EPISODE: + case MEDIA_TYPE_RADIO_STATION: + case MEDIA_TYPE_TRAILER: + case MEDIA_TYPE_TV_CHANNEL: + case MEDIA_TYPE_TV_SEASON: + case MEDIA_TYPE_TV_SERIES: + case MEDIA_TYPE_TV_SHOW: + case MEDIA_TYPE_VIDEO: + case MEDIA_TYPE_YEAR: + return FOLDER_TYPE_TITLES; + case MEDIA_TYPE_FOLDER_ALBUMS: + return FOLDER_TYPE_ALBUMS; + case MEDIA_TYPE_FOLDER_ARTISTS: + return FOLDER_TYPE_ARTISTS; + case MEDIA_TYPE_FOLDER_GENRES: + return FOLDER_TYPE_GENRES; + case MEDIA_TYPE_FOLDER_PLAYLISTS: + return FOLDER_TYPE_PLAYLISTS; + case MEDIA_TYPE_FOLDER_YEARS: + return FOLDER_TYPE_YEARS; + case MEDIA_TYPE_FOLDER_AUDIO_BOOKS: + case MEDIA_TYPE_FOLDER_MIXED: + case MEDIA_TYPE_FOLDER_TV_CHANNELS: + case MEDIA_TYPE_FOLDER_TV_SERIES: + case MEDIA_TYPE_FOLDER_TV_SHOWS: + case MEDIA_TYPE_FOLDER_PODCASTS: + case MEDIA_TYPE_MIXED: + default: + return FOLDER_TYPE_MIXED; + } + } + + private static @MediaType int getMediaTypeFromFolderType(@FolderType int folderType) { + switch (folderType) { + case FOLDER_TYPE_ALBUMS: + return MEDIA_TYPE_FOLDER_ALBUMS; + case FOLDER_TYPE_ARTISTS: + return MEDIA_TYPE_FOLDER_ARTISTS; + case FOLDER_TYPE_GENRES: + return MEDIA_TYPE_FOLDER_GENRES; + case FOLDER_TYPE_PLAYLISTS: + return MEDIA_TYPE_FOLDER_PLAYLISTS; + case FOLDER_TYPE_TITLES: + return MEDIA_TYPE_MIXED; + case FOLDER_TYPE_YEARS: + return MEDIA_TYPE_FOLDER_YEARS; + case FOLDER_TYPE_MIXED: + case FOLDER_TYPE_NONE: + default: + return MEDIA_TYPE_FOLDER_MIXED; + } + } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 2c1df81e40..2e1fd383c6 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -49,6 +49,7 @@ public class MediaMetadataTest { assertThat(mediaMetadata.trackNumber).isNull(); assertThat(mediaMetadata.totalTrackCount).isNull(); assertThat(mediaMetadata.folderType).isNull(); + assertThat(mediaMetadata.isBrowsable).isNull(); assertThat(mediaMetadata.isPlayable).isNull(); assertThat(mediaMetadata.recordingYear).isNull(); assertThat(mediaMetadata.recordingMonth).isNull(); @@ -115,6 +116,61 @@ public class MediaMetadataTest { assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); } + @Test + public void builderSetFolderType_toNone_setsIsBrowsableToFalse() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_NONE).build(); + + assertThat(mediaMetadata.isBrowsable).isFalse(); + } + + @Test + public void builderSetFolderType_toNotNone_setsIsBrowsableToTrueAndMatchingMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS).build(); + + assertThat(mediaMetadata.isBrowsable).isTrue(); + assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS); + } + + @Test + public void + builderSetFolderType_toNotNoneWithManualMediaType_setsIsBrowsableToTrueAndDoesNotOverrideMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder() + .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS) + .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) + .build(); + + assertThat(mediaMetadata.isBrowsable).isTrue(); + assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS); + } + + @Test + public void builderSetIsBrowsable_toTrueWithoutMediaType_setsFolderTypeToMixed() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(true).build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED); + } + + @Test + public void builderSetIsBrowsable_toTrueWithMediaType_setsFolderTypeToMatchMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder() + .setIsBrowsable(true) + .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS) + .build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_ARTISTS); + } + + @Test + public void builderSetFolderType_toFalse_setsFolderTypeToNone() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(false).build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_NONE); + } + private static MediaMetadata getFullyPopulatedMediaMetadata() { Bundle extras = new Bundle(); extras.putString(EXTRAS_KEY, EXTRAS_VALUE); @@ -135,6 +191,7 @@ public class MediaMetadataTest { .setTrackNumber(4) .setTotalTrackCount(12) .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) + .setIsBrowsable(true) .setIsPlayable(true) .setRecordingYear(2000) .setRecordingMonth(11) From 45664162d955b5703385ed3a0ee65501b4dddec0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 8 Dec 2022 10:09:55 +0000 Subject: [PATCH 045/104] Remove debug timeout multiplier. It looks like this was added accidentally in . PiperOrigin-RevId: 493834134 (cherry picked from commit f8e4e1765f3bc956e1e6e9eb17d72cb6c152282c) --- .../google/android/exoplayer2/robolectric/RobolectricUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/RobolectricUtil.java b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/RobolectricUtil.java index a46fad9c0d..655589f232 100644 --- a/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/RobolectricUtil.java +++ b/robolectricutils/src/main/java/com/google/android/exoplayer2/robolectric/RobolectricUtil.java @@ -94,7 +94,7 @@ public final class RobolectricUtil { */ public static void runLooperUntil(Looper looper, Supplier condition) throws TimeoutException { - runLooperUntil(looper, condition, DEFAULT_TIMEOUT_MS * 1000000, Clock.DEFAULT); + runLooperUntil(looper, condition, DEFAULT_TIMEOUT_MS, Clock.DEFAULT); } /** From 3dfbfd3f05ea3ab3fe452f3b491c186e60904559 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 8 Dec 2022 11:02:56 +0000 Subject: [PATCH 046/104] Clarify and correct allowed multi-threading for some Player methods Some Player methods like getting the Looper and adding listeners were always allowed to be called from any thread, but this is undocumented. This change makes the threading rules of these methods more explicit. Removing listeners was never meant to be called from another thread and we also don't support it safely because final callbacks may be triggered from the wrong thread. To find potential issues, we can assert the correct thread when releasing listeners. Finally, there is a potential race condition when calling addListener from a different thread at the same time as release, which may lead to a registered listener that could receive callbacks after the player is released. PiperOrigin-RevId: 493843981 (cherry picked from commit e9364b0f6e1a104de9cf4393daab521edc4eccf4) --- .../com/google/android/exoplayer2/Player.java | 8 +++ .../android/exoplayer2/SimpleBasePlayer.java | 3 +- .../android/exoplayer2/util/ListenerSet.java | 60 +++++++++++++++++-- .../google/android/exoplayer2/ExoPlayer.java | 34 +++++++---- .../android/exoplayer2/ExoPlayerImpl.java | 16 +++-- .../analytics/DefaultAnalyticsCollector.java | 14 +++++ .../android/exoplayer2/ExoPlayerTest.java | 14 ++++- 7 files changed, 123 insertions(+), 26 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index f26e033ce4..3272106ee8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -53,6 +53,10 @@ import java.util.List; * A media player interface defining traditional high-level functionality, such as the ability to * play, pause, seek and query properties of the currently playing media. * + *

    All methods must be called from a single {@linkplain #getApplicationLooper() application + * thread} unless indicated otherwise. Callbacks in registered listeners are called on the same + * thread. + * *

    This interface includes some convenience methods that can be implemented by calling other * methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting * {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of @@ -1532,6 +1536,8 @@ public interface Player { /** * Returns the {@link Looper} associated with the application thread that's used to access the * player and on which player events are received. + * + *

    This method can be called from any thread. */ Looper getApplicationLooper(); @@ -1541,6 +1547,8 @@ public interface Player { *

    The listener's methods will be called on the thread associated with {@link * #getApplicationLooper()}. * + *

    This method can be called from any thread. + * * @param listener The listener to register. */ void addListener(Listener listener); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 66a3d8ced9..ae6c89dbc1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -1981,8 +1981,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void removeListener(Listener listener) { - // Don't verify application thread. We allow calls to this method from any thread. - checkNotNull(listener); + verifyApplicationThreadAndInitState(); listeners.remove(listener); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java index 52c7c0bf9a..e1a6ec6504 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java @@ -15,9 +15,12 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.util.Assertions.checkState; + import android.os.Looper; import android.os.Message; import androidx.annotation.CheckResult; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import java.util.ArrayDeque; @@ -33,6 +36,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; *

    Events are also guaranteed to be only sent to the listeners registered at the time the event * was enqueued and haven't been removed since. * + *

    All methods must be called on the {@link Looper} passed to the constructor unless indicated + * otherwise. + * * @param The listener type. */ public final class ListenerSet { @@ -74,14 +80,18 @@ public final class ListenerSet { private final CopyOnWriteArraySet> listeners; private final ArrayDeque flushingEvents; private final ArrayDeque queuedEvents; + private final Object releasedLock; + @GuardedBy("releasedLock") private boolean released; + private boolean throwsWhenUsingWrongThread; + /** * Creates a new listener set. * * @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used - * to call all other methods of this class. + * to call all other methods of this class unless indicated otherwise. * @param clock A {@link Clock}. * @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent * during one {@link Looper} message queue iteration were handled by the listeners. @@ -98,17 +108,21 @@ public final class ListenerSet { this.clock = clock; this.listeners = listeners; this.iterationFinishedEvent = iterationFinishedEvent; + releasedLock = new Object(); flushingEvents = new ArrayDeque<>(); queuedEvents = new ArrayDeque<>(); // It's safe to use "this" because we don't send a message before exiting the constructor. @SuppressWarnings("nullness:methodref.receiver.bound") HandlerWrapper handler = clock.createHandler(looper, this::handleMessage); this.handler = handler; + throwsWhenUsingWrongThread = true; } /** * Copies the listener set. * + *

    This method can be called from any thread. + * * @param looper The new {@link Looper} for the copied listener set. * @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events * sent during one {@link Looper} message queue iteration were handled by the listeners. @@ -122,6 +136,8 @@ public final class ListenerSet { /** * Copies the listener set. * + *

    This method can be called from any thread. + * * @param looper The new {@link Looper} for the copied listener set. * @param clock The new {@link Clock} for the copied listener set. * @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events @@ -139,14 +155,18 @@ public final class ListenerSet { * *

    If a listener is already present, it will not be added again. * + *

    This method can be called from any thread. + * * @param listener The listener to be added. */ public void add(T listener) { - if (released) { - return; - } Assertions.checkNotNull(listener); - listeners.add(new ListenerHolder<>(listener)); + synchronized (releasedLock) { + if (released) { + return; + } + listeners.add(new ListenerHolder<>(listener)); + } } /** @@ -157,6 +177,7 @@ public final class ListenerSet { * @param listener The listener to be removed. */ public void remove(T listener) { + verifyCurrentThread(); for (ListenerHolder listenerHolder : listeners) { if (listenerHolder.listener.equals(listener)) { listenerHolder.release(iterationFinishedEvent); @@ -167,11 +188,13 @@ public final class ListenerSet { /** Removes all listeners from the set. */ public void clear() { + verifyCurrentThread(); listeners.clear(); } /** Returns the number of added listeners. */ public int size() { + verifyCurrentThread(); return listeners.size(); } @@ -183,6 +206,7 @@ public final class ListenerSet { * @param event The event. */ public void queueEvent(int eventFlag, Event event) { + verifyCurrentThread(); CopyOnWriteArraySet> listenerSnapshot = new CopyOnWriteArraySet<>(listeners); queuedEvents.add( () -> { @@ -194,6 +218,7 @@ public final class ListenerSet { /** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */ public void flushEvents() { + verifyCurrentThread(); if (queuedEvents.isEmpty()) { return; } @@ -232,11 +257,27 @@ public final class ListenerSet { *

    This will ensure no events are sent to any listener after this method has been called. */ public void release() { + verifyCurrentThread(); + synchronized (releasedLock) { + released = true; + } for (ListenerHolder listenerHolder : listeners) { listenerHolder.release(iterationFinishedEvent); } listeners.clear(); - released = true; + } + + /** + * Sets whether methods throw when using the wrong thread. + * + *

    Do not use this method unless to support legacy use cases. + * + * @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread. + * @deprecated Do not use this method and ensure all calls are made from the correct thread. + */ + @Deprecated + public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { + this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; } private boolean handleMessage(Message message) { @@ -252,6 +293,13 @@ public final class ListenerSet { return true; } + private void verifyCurrentThread() { + if (!throwsWhenUsingWrongThread) { + return; + } + checkState(Thread.currentThread() == handler.getLooper().getThread()); + } + private static final class ListenerHolder { public final T listener; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 6889337910..9960f6c4ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -125,15 +125,15 @@ import java.util.List; * threading model"> * *

      - *
    • ExoPlayer instances must be accessed from a single application thread. For the vast - * majority of cases this should be the application's main thread. Using the application's - * main thread is also a requirement when using ExoPlayer's UI components or the IMA - * extension. The thread on which an ExoPlayer instance must be accessed can be explicitly - * specified by passing a `Looper` when creating the player. If no `Looper` is specified, then - * the `Looper` of the thread that the player is created on is used, or if that thread does - * not have a `Looper`, the `Looper` of the application's main thread is used. In all cases - * the `Looper` of the thread from which the player must be accessed can be queried using - * {@link #getApplicationLooper()}. + *
    • ExoPlayer instances must be accessed from a single application thread unless indicated + * otherwise. For the vast majority of cases this should be the application's main thread. + * Using the application's main thread is also a requirement when using ExoPlayer's UI + * components or the IMA extension. The thread on which an ExoPlayer instance must be accessed + * can be explicitly specified by passing a `Looper` when creating the player. If no `Looper` + * is specified, then the `Looper` of the thread that the player is created on is used, or if + * that thread does not have a `Looper`, the `Looper` of the application's main thread is + * used. In all cases the `Looper` of the thread from which the player must be accessed can be + * queried using {@link #getApplicationLooper()}. *
    • Registered listeners are called on the thread associated with {@link * #getApplicationLooper()}. Note that this means registered listeners are called on the same * thread which must be used to access the player. @@ -1187,6 +1187,8 @@ public interface ExoPlayer extends Player { /** * Adds a listener to receive audio offload events. * + *

      This method can be called from any thread. + * * @param listener The listener to register. */ void addAudioOffloadListener(AudioOffloadListener listener); @@ -1204,6 +1206,8 @@ public interface ExoPlayer extends Player { /** * Adds an {@link AnalyticsListener} to receive analytics events. * + *

      This method can be called from any thread. + * * @param listener The listener to be added. */ void addAnalyticsListener(AnalyticsListener listener); @@ -1263,10 +1267,18 @@ public interface ExoPlayer extends Player { @Deprecated TrackSelectionArray getCurrentTrackSelections(); - /** Returns the {@link Looper} associated with the playback thread. */ + /** + * Returns the {@link Looper} associated with the playback thread. + * + *

      This method may be called from any thread. + */ Looper getPlaybackLooper(); - /** Returns the {@link Clock} used for playback. */ + /** + * Returns the {@link Clock} used for playback. + * + *

      This method can be called from any thread. + */ Clock getClock(); /** 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 da7e5eb31c..fa6ba6b686 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 @@ -58,6 +58,7 @@ import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.Renderer.MessageType; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; +import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector; import com.google.android.exoplayer2.analytics.MediaMetricsListener; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.audio.AudioAttributes; @@ -468,7 +469,7 @@ import java.util.concurrent.TimeoutException; @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. + verifyApplicationThread(); audioOffloadListeners.remove(listener); } @@ -1476,7 +1477,7 @@ import java.util.concurrent.TimeoutException; @Override public void removeAnalyticsListener(AnalyticsListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. + verifyApplicationThread(); analyticsCollector.removeListener(checkNotNull(listener)); } @@ -1593,9 +1594,8 @@ import java.util.concurrent.TimeoutException; @Override public void removeListener(Listener listener) { - // Don't verify application thread. We allow calls to this method from any thread. - checkNotNull(listener); - listeners.remove(listener); + verifyApplicationThread(); + listeners.remove(checkNotNull(listener)); } @Override @@ -1678,8 +1678,14 @@ import java.util.concurrent.TimeoutException; return false; } + @SuppressWarnings("deprecation") // Calling deprecated methods. /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; + listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); + if (analyticsCollector instanceof DefaultAnalyticsCollector) { + ((DefaultAnalyticsCollector) analyticsCollector) + .setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); + } } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.java index 14d1f546cd..4d3eda9373 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultAnalyticsCollector.java @@ -94,6 +94,20 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector { eventTimes = new SparseArray<>(); } + /** + * Sets whether methods throw when using the wrong thread. + * + *

      Do not use this method unless to support legacy use cases. + * + * @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread. + * @deprecated Do not use this method and ensure all calls are made from the correct thread. + */ + @SuppressWarnings("deprecation") // Calling deprecated method. + @Deprecated + public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { + listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); + } + @Override @CallSuper public void addListener(AnalyticsListener listener) { 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 d760ea8c0e..49669f757f 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 @@ -11996,10 +11996,20 @@ public final class ExoPlayerTest { @Test @Config(sdk = Config.ALL_SDKS) - public void builder_inBackgroundThread_doesNotThrow() throws Exception { + public void builder_inBackgroundThreadWithAllowedAnyThreadMethods_doesNotThrow() + throws Exception { Thread builderThread = new Thread( - () -> new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build()); + () -> { + ExoPlayer player = + new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(); + player.addListener(new Listener() {}); + player.addAnalyticsListener(new AnalyticsListener() {}); + player.addAudioOffloadListener(new ExoPlayer.AudioOffloadListener() {}); + player.getClock(); + player.getApplicationLooper(); + player.getPlaybackLooper(); + }); AtomicReference builderThrow = new AtomicReference<>(); builderThread.setUncaughtExceptionHandler((thread, throwable) -> builderThrow.set(throwable)); From b9365f5563445570c6ad92607a31524be56ad04f Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 13 Dec 2022 09:04:04 +0000 Subject: [PATCH 047/104] Forward seek command details to seekTo method in BasePlayer BasePlayer simplifies implementations by handling all the various seek methods and forwarding to a single method that can then be implemented by subclasses. However, this loses the information about the concrete entry point used for seeking, which is relevant when the subclass wants to verify or filter by Player.Command. This can be improved by adding the command as a new parameter. Since we have to change the method anyway, we can also incorporate the boolean flag about whether the current item is repeated to avoid the separate method. PiperOrigin-RevId: 494948094 (cherry picked from commit 6e0f1f10b3666e6c2c74fc5a154dec3f0e03fecb) --- .../exoplayer2/ext/cast/CastPlayer.java | 8 +- .../google/android/exoplayer2/BasePlayer.java | 137 +++++--- .../android/exoplayer2/SimpleBasePlayer.java | 15 +- .../android/exoplayer2/BasePlayerTest.java | 318 ++++++++++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 95 +++--- .../android/exoplayer2/SimpleExoPlayer.java | 12 +- .../exoplayer2/testutil/StubPlayer.java | 6 +- 7 files changed, 483 insertions(+), 108 deletions(-) create mode 100644 library/common/src/test/java/com/google/android/exoplayer2/BasePlayerTest.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 73ced8b10d..a87fe9a437 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.cast; +import static androidx.annotation.VisibleForTesting.PROTECTED; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.min; @@ -397,7 +398,12 @@ public final class CastPlayer extends BasePlayer { // don't implement onPositionDiscontinuity(). @SuppressWarnings("deprecation") @Override - public void seekTo(int mediaItemIndex, long positionMs) { + @VisibleForTesting(otherwise = PROTECTED) + public void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { MediaStatus mediaStatus = getMediaStatus(); // We assume the default position is 0. There is no support for seeking to the default position // in RemoteMediaClient. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index e77a654b5b..0632dff53f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -15,13 +15,14 @@ */ package com.google.android.exoplayer2; +import static androidx.annotation.VisibleForTesting.PROTECTED; import static java.lang.Math.max; import static java.lang.Math.min; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.ForOverride; import java.util.List; /** Abstract base {@link Player} which implements common implementation independent methods. */ @@ -119,27 +120,23 @@ public abstract class BasePlayer implements Player { @Override public final void seekToDefaultPosition() { - seekToDefaultPosition(getCurrentMediaItemIndex()); + seekToDefaultPositionInternal( + getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_DEFAULT_POSITION); } @Override public final void seekToDefaultPosition(int mediaItemIndex) { - seekTo(mediaItemIndex, /* positionMs= */ C.TIME_UNSET); - } - - @Override - public final void seekTo(long positionMs) { - seekTo(getCurrentMediaItemIndex(), positionMs); + seekToDefaultPositionInternal(mediaItemIndex, Player.COMMAND_SEEK_TO_MEDIA_ITEM); } @Override public final void seekBack() { - seekToOffset(-getSeekBackIncrement()); + seekToOffset(-getSeekBackIncrement(), Player.COMMAND_SEEK_BACK); } @Override public final void seekForward() { - seekToOffset(getSeekForwardIncrement()); + seekToOffset(getSeekForwardIncrement(), Player.COMMAND_SEEK_FORWARD); } /** @@ -185,15 +182,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekToPreviousMediaItem() { - int previousMediaItemIndex = getPreviousMediaItemIndex(); - if (previousMediaItemIndex == C.INDEX_UNSET) { - return; - } - if (previousMediaItemIndex == getCurrentMediaItemIndex()) { - repeatCurrentMediaItem(); - } else { - seekToDefaultPosition(previousMediaItemIndex); - } + seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM); } @Override @@ -205,12 +194,12 @@ public abstract class BasePlayer implements Player { boolean hasPreviousMediaItem = hasPreviousMediaItem(); if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) { if (hasPreviousMediaItem) { - seekToPreviousMediaItem(); + seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS); } } else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) { - seekToPreviousMediaItem(); + seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS); } else { - seekTo(/* positionMs= */ 0); + seekToCurrentItem(/* positionMs= */ 0, Player.COMMAND_SEEK_TO_PREVIOUS); } } @@ -257,15 +246,7 @@ public abstract class BasePlayer implements Player { @Override public final void seekToNextMediaItem() { - int nextMediaItemIndex = getNextMediaItemIndex(); - if (nextMediaItemIndex == C.INDEX_UNSET) { - return; - } - if (nextMediaItemIndex == getCurrentMediaItemIndex()) { - repeatCurrentMediaItem(); - } else { - seekToDefaultPosition(nextMediaItemIndex); - } + seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); } @Override @@ -275,12 +256,42 @@ public abstract class BasePlayer implements Player { return; } if (hasNextMediaItem()) { - seekToNextMediaItem(); + seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT); } else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) { - seekToDefaultPosition(); + seekToDefaultPositionInternal(getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_NEXT); } } + @Override + public final void seekTo(long positionMs) { + seekToCurrentItem(positionMs, Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + } + + @Override + public final void seekTo(int mediaItemIndex, long positionMs) { + seekTo( + mediaItemIndex, + positionMs, + Player.COMMAND_SEEK_TO_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + /** + * Seeks to a position in the specified {@link MediaItem}. + * + * @param mediaItemIndex The index of the {@link MediaItem}. + * @param positionMs The seek position in the specified {@link MediaItem} in milliseconds, or + * {@link C#TIME_UNSET} to seek to the media item's default position. + * @param seekCommand The {@link Player.Command} used to trigger the seek. + * @param isRepeatingCurrentItem Whether this seeks repeats the current item. + */ + @VisibleForTesting(otherwise = PROTECTED) + public abstract void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem); + @Override public final void setPlaybackSpeed(float speed) { setPlaybackParameters(getPlaybackParameters().withSpeed(speed)); @@ -435,29 +446,63 @@ public abstract class BasePlayer implements Player { : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } - /** - * Repeat the current media item. - * - *

      The default implementation seeks to the default position in the current item, which can be - * overridden for additional handling. - */ - @ForOverride - protected void repeatCurrentMediaItem() { - seekToDefaultPosition(); - } - private @RepeatMode int getRepeatModeForNavigation() { @RepeatMode int repeatMode = getRepeatMode(); return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; } - private void seekToOffset(long offsetMs) { + private void seekToCurrentItem(long positionMs, @Player.Command int seekCommand) { + seekTo( + getCurrentMediaItemIndex(), positionMs, seekCommand, /* isRepeatingCurrentItem= */ false); + } + + private void seekToOffset(long offsetMs, @Player.Command int seekCommand) { long positionMs = getCurrentPosition() + offsetMs; long durationMs = getDuration(); if (durationMs != C.TIME_UNSET) { positionMs = min(positionMs, durationMs); } positionMs = max(positionMs, 0); - seekTo(positionMs); + seekToCurrentItem(positionMs, seekCommand); + } + + private void seekToDefaultPositionInternal(int mediaItemIndex, @Player.Command int seekCommand) { + seekTo( + mediaItemIndex, + /* positionMs= */ C.TIME_UNSET, + seekCommand, + /* isRepeatingCurrentItem= */ false); + } + + private void seekToNextMediaItemInternal(@Player.Command int seekCommand) { + int nextMediaItemIndex = getNextMediaItemIndex(); + if (nextMediaItemIndex == C.INDEX_UNSET) { + return; + } + if (nextMediaItemIndex == getCurrentMediaItemIndex()) { + repeatCurrentMediaItem(seekCommand); + } else { + seekToDefaultPositionInternal(nextMediaItemIndex, seekCommand); + } + } + + private void seekToPreviousMediaItemInternal(@Player.Command int seekCommand) { + int previousMediaItemIndex = getPreviousMediaItemIndex(); + if (previousMediaItemIndex == C.INDEX_UNSET) { + return; + } + if (previousMediaItemIndex == getCurrentMediaItemIndex()) { + repeatCurrentMediaItem(seekCommand); + } else { + seekToDefaultPositionInternal(previousMediaItemIndex, seekCommand); + } + } + + private void repeatCurrentMediaItem(@Player.Command int seekCommand) { + seekTo( + getCurrentMediaItemIndex(), + /* positionMs= */ C.TIME_UNSET, + seekCommand, + /* isRepeatingCurrentItem= */ true); } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index ae6c89dbc1..64fbdd1853 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import static androidx.annotation.VisibleForTesting.PROTECTED; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -32,6 +33,7 @@ import android.view.TextureView; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.ads.AdPlaybackState; @@ -2136,13 +2138,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { } @Override - public final void seekTo(int mediaItemIndex, long positionMs) { - // TODO: implement. - throw new IllegalStateException(); - } - - @Override - protected final void repeatCurrentMediaItem() { + @VisibleForTesting(otherwise = PROTECTED) + public final void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { // TODO: implement. throw new IllegalStateException(); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/BasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/BasePlayerTest.java new file mode 100644 index 0000000000..b1080ac8c6 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/BasePlayerTest.java @@ -0,0 +1,318 @@ +/* + * Copyright 2022 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 static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.testutil.FakeTimeline; +import com.google.android.exoplayer2.testutil.StubPlayer; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link BasePlayer}. */ +@RunWith(AndroidJUnit4.class) +public class BasePlayerTest { + + @Test + public void seekTo_withIndexAndPosition_usesCommandSeekToMediaItem() { + BasePlayer player = spy(new TestBasePlayer()); + + player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 4000); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 2, + /* positionMs= */ 4000, + Player.COMMAND_SEEK_TO_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekTo_withPosition_usesCommandSeekInCurrentMediaItem() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + }); + + player.seekTo(/* positionMs= */ 4000); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 1, + /* positionMs= */ 4000, + Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToDefaultPosition_withIndex_usesCommandSeekToMediaItem() { + BasePlayer player = spy(new TestBasePlayer()); + + player.seekToDefaultPosition(/* mediaItemIndex= */ 2); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 2, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToDefaultPosition_withoutIndex_usesCommandSeekToDefaultPosition() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + }); + + player.seekToDefaultPosition(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 1, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_DEFAULT_POSITION, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToNext_usesCommandSeekToNext() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + }); + + player.seekToNext(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 2, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_NEXT, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToNextMediaItem_usesCommandSeekToNextMediaItem() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + }); + + player.seekToNextMediaItem(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 2, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekForward_usesCommandSeekForward() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public long getSeekForwardIncrement() { + return 2000; + } + + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + + @Override + public long getCurrentPosition() { + return 5000; + } + }); + + player.seekForward(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 1, + /* positionMs= */ 7000, + Player.COMMAND_SEEK_FORWARD, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToPrevious_usesCommandSeekToPrevious() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + + @Override + public long getMaxSeekToPreviousPosition() { + return 4000; + } + + @Override + public long getCurrentPosition() { + return 2000; + } + }); + + player.seekToPrevious(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 0, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_PREVIOUS, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekToPreviousMediaItem_usesCommandSeekToPreviousMediaItem() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + }); + + player.seekToPreviousMediaItem(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 0, + /* positionMs= */ C.TIME_UNSET, + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, + /* isRepeatingCurrentItem= */ false); + } + + @Test + public void seekBack_usesCommandSeekBack() { + BasePlayer player = + spy( + new TestBasePlayer() { + @Override + public long getSeekBackIncrement() { + return 2000; + } + + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + + @Override + public long getCurrentPosition() { + return 5000; + } + }); + + player.seekBack(); + + verify(player) + .seekTo( + /* mediaItemIndex= */ 1, + /* positionMs= */ 3000, + Player.COMMAND_SEEK_BACK, + /* isRepeatingCurrentItem= */ false); + } + + private static class TestBasePlayer extends StubPlayer { + + @Override + public void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { + // Do nothing. + } + + @Override + public long getSeekBackIncrement() { + return 2000; + } + + @Override + public long getSeekForwardIncrement() { + return 2000; + } + + @Override + public long getMaxSeekToPreviousPosition() { + return 2000; + } + + @Override + public Timeline getCurrentTimeline() { + return new FakeTimeline(/* windowCount= */ 3); + } + + @Override + public int getCurrentMediaItemIndex() { + return 1; + } + + @Override + public long getCurrentPosition() { + return 5000; + } + + @Override + public long getDuration() { + return 20000; + } + + @Override + public boolean isPlayingAd() { + return false; + } + + @Override + public int getRepeatMode() { + return Player.REPEAT_MODE_OFF; + } + + @Override + public boolean getShuffleModeEnabled() { + return false; + } + } +} 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 fa6ba6b686..2adf381621 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 @@ -812,16 +812,51 @@ import java.util.concurrent.TimeoutException; } @Override - protected void repeatCurrentMediaItem() { + public void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { verifyApplicationThread(); - seekToInternal( - getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET, /* repeatMediaItem= */ true); - } - - @Override - public void seekTo(int mediaItemIndex, long positionMs) { - verifyApplicationThread(); - seekToInternal(mediaItemIndex, positionMs, /* repeatMediaItem= */ false); + analyticsCollector.notifySeekStarted(); + Timeline timeline = playbackInfo.timeline; + if (mediaItemIndex < 0 + || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { + throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); + } + pendingOperationAcks++; + if (isPlayingAd()) { + // TODO: Investigate adding support for seeking during ads. This is complicated to do in + // general because the midroll ad preceding the seek destination must be played before the + // content position can be played, if a different ad is playing at the moment. + Log.w(TAG, "seekTo ignored because an ad is playing"); + ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate = + new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo); + playbackInfoUpdate.incrementPendingOperationAcks(1); + playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); + return; + } + @Player.State + int newPlaybackState = + getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING; + int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); + PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState); + newPlaybackInfo = + maskTimelineAndPosition( + newPlaybackInfo, + timeline, + maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs)); + internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs)); + updatePlaybackInfo( + newPlaybackInfo, + /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, + /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, + /* seekProcessed= */ true, + /* positionDiscontinuity= */ true, + /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), + oldMaskingMediaItemIndex, + isRepeatingCurrentItem); } @Override @@ -2685,48 +2720,6 @@ import java.util.concurrent.TimeoutException; } } - private void seekToInternal(int mediaItemIndex, long positionMs, boolean repeatMediaItem) { - analyticsCollector.notifySeekStarted(); - Timeline timeline = playbackInfo.timeline; - if (mediaItemIndex < 0 - || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); - } - pendingOperationAcks++; - if (isPlayingAd()) { - // TODO: Investigate adding support for seeking during ads. This is complicated to do in - // general because the midroll ad preceding the seek destination must be played before the - // content position can be played, if a different ad is playing at the moment. - Log.w(TAG, "seekTo ignored because an ad is playing"); - ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate = - new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo); - playbackInfoUpdate.incrementPendingOperationAcks(1); - playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); - return; - } - @Player.State - int newPlaybackState = - getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING; - int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); - PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState); - newPlaybackInfo = - maskTimelineAndPosition( - newPlaybackInfo, - timeline, - maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs)); - internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs)); - updatePlaybackInfo( - newPlaybackInfo, - /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, - /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ true, - /* positionDiscontinuity= */ true, - /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, - /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), - oldMaskingMediaItemIndex, - repeatMediaItem); - } - private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) { return new DeviceInfo( DeviceInfo.PLAYBACK_TYPE_LOCAL, 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 589780dfb6..04e703fe95 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 @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2; +import static androidx.annotation.VisibleForTesting.PROTECTED; + import android.content.Context; import android.media.AudioDeviceInfo; import android.os.Looper; @@ -993,10 +995,16 @@ public class SimpleExoPlayer extends BasePlayer return player.isLoading(); } + @SuppressWarnings("ForOverride") // Forwarding to ForOverride method in ExoPlayerImpl. @Override - public void seekTo(int mediaItemIndex, long positionMs) { + @VisibleForTesting(otherwise = PROTECTED) + public void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { blockUntilConstructorFinished(); - player.seekTo(mediaItemIndex, positionMs); + player.seekTo(mediaItemIndex, positionMs, seekCommand, isRepeatingCurrentItem); } @Override diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java index f60a65ce32..4065dc66b6 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubPlayer.java @@ -145,7 +145,11 @@ public class StubPlayer extends BasePlayer { } @Override - public void seekTo(int mediaItemIndex, long positionMs) { + public void seekTo( + int mediaItemIndex, + long positionMs, + @Player.Command int seekCommand, + boolean isRepeatingCurrentItem) { throw new UnsupportedOperationException(); } From 47b811e3b92490ed08306ac427a330d7d4d753c8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 13 Dec 2022 11:37:20 +0000 Subject: [PATCH 048/104] Reset isLoading when calling SimpleBasePlayer.stop/release isLoading is not allowed to be true when IDLE, so we have to set to false when stopping in case it was set to true before. PiperOrigin-RevId: 494975405 (cherry picked from commit e4f0b73aa325d096b493bd646c63bc6a4bf5880b) --- .../com/google/android/exoplayer2/SimpleBasePlayer.java | 2 ++ .../google/android/exoplayer2/SimpleBasePlayerTest.java | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 64fbdd1853..fbf3e081f1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -2203,6 +2203,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { .setTotalBufferedDurationMs(PositionSupplier.ZERO) .setContentBufferedPositionMs(state.contentPositionMsSupplier) .setAdBufferedPositionMs(state.adPositionMsSupplier) + .setIsLoading(false) .build()); } @@ -2234,6 +2235,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { .setTotalBufferedDurationMs(PositionSupplier.ZERO) .setContentBufferedPositionMs(state.contentPositionMsSupplier) .setAdBufferedPositionMs(state.adPositionMsSupplier) + .setIsLoading(false) .build(); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index db3c3d16ce..65ac7a13bb 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -2102,6 +2102,7 @@ public class SimpleBasePlayerTest { .setPlaylist( ImmutableList.of( new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .setIsLoading(true) .build(); // Additionally set the repeat mode to see a difference between the placeholder and new state. State updatedState = @@ -2109,6 +2110,7 @@ public class SimpleBasePlayerTest { .buildUpon() .setPlaybackState(Player.STATE_IDLE) .setRepeatMode(Player.REPEAT_MODE_ALL) + .setIsLoading(false) .build(); SettableFuture future = SettableFuture.create(); SimpleBasePlayer player = @@ -2131,9 +2133,12 @@ public class SimpleBasePlayerTest { // Verify placeholder state and listener calls. assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_OFF); + assertThat(player.isLoading()).isFalse(); verify(listener).onPlaybackStateChanged(Player.STATE_IDLE); verify(listener) .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE); + verify(listener).onIsLoadingChanged(false); + verify(listener).onLoadingChanged(false); verifyNoMoreInteractions(listener); future.set(null); @@ -2218,6 +2223,7 @@ public class SimpleBasePlayerTest { .setPlaylist( ImmutableList.of( new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .setIsLoading(true) .build(); // Additionally set the repeat mode to see a difference between the placeholder and new state. State updatedState = state.buildUpon().setRepeatMode(Player.REPEAT_MODE_ALL).build(); @@ -2239,8 +2245,9 @@ public class SimpleBasePlayerTest { player.release(); - // Verify initial change to IDLE without listener call. + // Verify initial change to IDLE and !isLoading without listener call. assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE); + assertThat(player.isLoading()).isFalse(); verifyNoMoreInteractions(listener); future.set(null); From aa2158d5c841e6a17d0d6376d0df7fe56897fbaa Mon Sep 17 00:00:00 2001 From: rohks Date: Tue, 13 Dec 2022 14:27:54 +0000 Subject: [PATCH 049/104] Document the reason for defining private method `defaultIfNull` PiperOrigin-RevId: 495004732 (cherry picked from commit c3ca71fda738772dccc5a5e07293c884d2194f0a) --- .../main/java/com/google/android/exoplayer2/Format.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index ea3b552e1d..e55d17490a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -1668,6 +1668,14 @@ public final class Format implements Bundleable { + Integer.toString(initialisationDataIndex, Character.MAX_RADIX); } + /** + * Utility method to get {@code defaultValue} if {@code value} is {@code null}. {@code + * defaultValue} can be {@code null}. + * + *

      Note: Current implementations of getters in {@link Bundle}, for example {@link + * Bundle#getString(String, String)} does not allow the defaultValue to be {@code null}, hence the + * need for this method. + */ @Nullable private static T defaultIfNull(@Nullable T value, @Nullable T defaultValue) { return value != null ? value : defaultValue; From 951fea231d5f3f339075b1a9a885733ade2d68b0 Mon Sep 17 00:00:00 2001 From: rohks Date: Tue, 13 Dec 2022 18:09:51 +0000 Subject: [PATCH 050/104] Remove parameters with default values from bundle in `MediaItem` This improves the time taken to construct PlayerInfo from bundle from ~600ms to ~450ms. PiperOrigin-RevId: 495055355 (cherry picked from commit 7de47fe2a1493da1b0720ddeb357052d2b1dc5bb) --- .../google/android/exoplayer2/MediaItem.java | 97 ++++++++++---- .../android/exoplayer2/MediaItemTest.java | 119 +++++++++++++++++- 2 files changed, 188 insertions(+), 28 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index cd104145d4..456149b8c7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -1094,7 +1094,7 @@ public final class MediaItem implements Bundleable { private float minPlaybackSpeed; private float maxPlaybackSpeed; - /** Constructs an instance. */ + /** Creates a new instance with default values. */ public Builder() { this.targetOffsetMs = C.TIME_UNSET; this.minOffsetMs = C.TIME_UNSET; @@ -1296,11 +1296,21 @@ public final class MediaItem implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs); - bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs); - bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs); - bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed); - bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed); + if (targetOffsetMs != UNSET.targetOffsetMs) { + bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs); + } + if (minOffsetMs != UNSET.minOffsetMs) { + bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs); + } + if (maxOffsetMs != UNSET.maxOffsetMs) { + bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs); + } + if (minPlaybackSpeed != UNSET.minPlaybackSpeed) { + bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed); + } + if (maxPlaybackSpeed != UNSET.maxPlaybackSpeed) { + bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed); + } return bundle; } @@ -1309,13 +1319,17 @@ public final class MediaItem implements Bundleable { bundle -> new LiveConfiguration( bundle.getLong( - keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), - bundle.getLong(keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), - bundle.getLong(keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET), + keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ UNSET.targetOffsetMs), + bundle.getLong( + keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ UNSET.minOffsetMs), + bundle.getLong( + keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ UNSET.maxOffsetMs), bundle.getFloat( - keyForField(FIELD_MIN_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET), + keyForField(FIELD_MIN_PLAYBACK_SPEED), + /* defaultValue= */ UNSET.minPlaybackSpeed), bundle.getFloat( - keyForField(FIELD_MAX_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET)); + keyForField(FIELD_MAX_PLAYBACK_SPEED), + /* defaultValue= */ UNSET.maxPlaybackSpeed)); private static String keyForField(@LiveConfiguration.FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); @@ -1554,7 +1568,7 @@ public final class MediaItem implements Bundleable { private boolean relativeToDefaultPosition; private boolean startsAtKeyFrame; - /** Constructs an instance. */ + /** Creates a new instance with default values. */ public Builder() { endPositionMs = C.TIME_END_OF_SOURCE; } @@ -1727,11 +1741,22 @@ public final class MediaItem implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs); - bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs); - bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow); - bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition); - bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame); + if (startPositionMs != UNSET.startPositionMs) { + bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs); + } + if (endPositionMs != UNSET.endPositionMs) { + bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs); + } + if (relativeToLiveWindow != UNSET.relativeToLiveWindow) { + bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow); + } + if (relativeToDefaultPosition != UNSET.relativeToDefaultPosition) { + bundle.putBoolean( + keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition); + } + if (startsAtKeyFrame != UNSET.startsAtKeyFrame) { + bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame); + } return bundle; } @@ -1740,17 +1765,25 @@ public final class MediaItem implements Bundleable { bundle -> new ClippingConfiguration.Builder() .setStartPositionMs( - bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0)) + bundle.getLong( + keyForField(FIELD_START_POSITION_MS), + /* defaultValue= */ UNSET.startPositionMs)) .setEndPositionMs( bundle.getLong( keyForField(FIELD_END_POSITION_MS), - /* defaultValue= */ C.TIME_END_OF_SOURCE)) + /* defaultValue= */ UNSET.endPositionMs)) .setRelativeToLiveWindow( - bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false)) + bundle.getBoolean( + keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), + /* defaultValue= */ UNSET.relativeToLiveWindow)) .setRelativeToDefaultPosition( - bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false)) + bundle.getBoolean( + keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), + /* defaultValue= */ UNSET.relativeToDefaultPosition)) .setStartsAtKeyFrame( - bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)) + bundle.getBoolean( + keyForField(FIELD_STARTS_AT_KEY_FRAME), + /* defaultValue= */ UNSET.startsAtKeyFrame)) .buildClippingProperties(); private static String keyForField(@ClippingConfiguration.FieldNumber int field) { @@ -2033,11 +2066,21 @@ public final class MediaItem implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId); - bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); - bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); - bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle()); - bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle()); + if (!mediaId.equals(DEFAULT_MEDIA_ID)) { + bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId); + } + if (!liveConfiguration.equals(LiveConfiguration.UNSET)) { + bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); + } + if (!mediaMetadata.equals(MediaMetadata.EMPTY)) { + bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); + } + if (!clippingConfiguration.equals(ClippingConfiguration.UNSET)) { + bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle()); + } + if (!requestMetadata.equals(RequestMetadata.EMPTY)) { + bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle()); + } return bundle; } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index 8b2ac0e054..c354ceec99 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -362,10 +362,12 @@ public class MediaItemTest { } @Test - public void clippingConfigurationDefaults() { + public void createDefaultClippingConfigurationInstance_checksDefaultValues() { MediaItem.ClippingConfiguration clippingConfiguration = new MediaItem.ClippingConfiguration.Builder().build(); + // Please refrain from altering default values since doing so would cause issues with backwards + // compatibility. assertThat(clippingConfiguration.startPositionMs).isEqualTo(0L); assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE); assertThat(clippingConfiguration.relativeToLiveWindow).isFalse(); @@ -374,6 +376,32 @@ public class MediaItemTest { assertThat(clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET); } + @Test + public void createDefaultClippingConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + MediaItem.ClippingConfiguration clippingConfiguration = + new MediaItem.ClippingConfiguration.Builder().build(); + + MediaItem.ClippingConfiguration clippingConfigurationFromBundle = + MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfiguration.toBundle()); + + assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration); + } + + @Test + public void createClippingConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + // Creates instance by setting some non-default values + MediaItem.ClippingConfiguration clippingConfiguration = + new MediaItem.ClippingConfiguration.Builder() + .setStartPositionMs(1000L) + .setStartsAtKeyFrame(true) + .build(); + + MediaItem.ClippingConfiguration clippingConfigurationFromBundle = + MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfiguration.toBundle()); + + assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration); + } + @Test public void clippingConfigurationBuilder_throwsOnInvalidValues() { MediaItem.ClippingConfiguration.Builder clippingConfigurationBuilder = @@ -516,6 +544,47 @@ public class MediaItemTest { assertThat(mediaItem.mediaMetadata).isEqualTo(mediaMetadata); } + @Test + public void createDefaultLiveConfigurationInstance_checksDefaultValues() { + MediaItem.LiveConfiguration liveConfiguration = + new MediaItem.LiveConfiguration.Builder().build(); + + // Please refrain from altering default values since doing so would cause issues with backwards + // compatibility. + assertThat(liveConfiguration.targetOffsetMs).isEqualTo(C.TIME_UNSET); + assertThat(liveConfiguration.minOffsetMs).isEqualTo(C.TIME_UNSET); + assertThat(liveConfiguration.maxOffsetMs).isEqualTo(C.TIME_UNSET); + assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(liveConfiguration).isEqualTo(MediaItem.LiveConfiguration.UNSET); + } + + @Test + public void createDefaultLiveConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + MediaItem.LiveConfiguration liveConfiguration = + new MediaItem.LiveConfiguration.Builder().build(); + + MediaItem.LiveConfiguration liveConfigurationFromBundle = + MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfiguration.toBundle()); + + assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration); + } + + @Test + public void createLiveConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + // Creates instance by setting some non-default values + MediaItem.LiveConfiguration liveConfiguration = + new MediaItem.LiveConfiguration.Builder() + .setTargetOffsetMs(10_000) + .setMaxPlaybackSpeed(2f) + .build(); + + MediaItem.LiveConfiguration liveConfigurationFromBundle = + MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfiguration.toBundle()); + + assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration); + } + @Test public void builderSetLiveConfiguration() { MediaItem mediaItem = @@ -749,4 +818,52 @@ public class MediaItemTest { assertThat(mediaItem.localConfiguration).isNotNull(); assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull(); } + + @Test + public void createDefaultMediaItemInstance_checksDefaultValues() { + MediaItem mediaItem = new MediaItem.Builder().build(); + + // Please refrain from altering default values since doing so would cause issues with backwards + // compatibility. + assertThat(mediaItem.mediaId).isEqualTo(MediaItem.DEFAULT_MEDIA_ID); + assertThat(mediaItem.liveConfiguration).isEqualTo(MediaItem.LiveConfiguration.UNSET); + assertThat(mediaItem.mediaMetadata).isEqualTo(MediaMetadata.EMPTY); + assertThat(mediaItem.clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET); + assertThat(mediaItem.requestMetadata).isEqualTo(RequestMetadata.EMPTY); + assertThat(mediaItem).isEqualTo(MediaItem.EMPTY); + } + + @Test + public void createDefaultMediaItemInstance_roundTripViaBundle_yieldsEqualInstance() { + MediaItem mediaItem = new MediaItem.Builder().build(); + + MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle()); + + assertThat(mediaItemFromBundle).isEqualTo(mediaItem); + } + + @Test + public void createMediaItemInstance_roundTripViaBundle_yieldsEqualInstance() { + // Creates instance by setting some non-default values + MediaItem mediaItem = + new MediaItem.Builder() + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder() + .setTargetOffsetMs(20_000) + .setMinOffsetMs(2_222) + .setMaxOffsetMs(4_444) + .setMinPlaybackSpeed(.9f) + .setMaxPlaybackSpeed(1.1f) + .build()) + .setRequestMetadata( + new RequestMetadata.Builder() + .setMediaUri(Uri.parse("http://test.test")) + .setSearchQuery("search") + .build()) + .build(); + + MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle()); + + assertThat(mediaItemFromBundle).isEqualTo(mediaItem); + } } From 0e921d1a7a979c328e2f0048a39c19198065bd71 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 14 Dec 2022 12:42:43 +0000 Subject: [PATCH 051/104] Clear one-off events from state as soon as they are triggered. This ensures they are not accidentally triggered again when the state is rebuilt with a buildUpon method. PiperOrigin-RevId: 495280711 (cherry picked from commit fa5aaf958d616d831d50fdaceb02d3ff3cbad0fa) --- .../android/exoplayer2/SimpleBasePlayer.java | 15 +++- .../exoplayer2/SimpleBasePlayerTest.java | 76 +++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index fbf3e081f1..ebf584dda0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -2891,6 +2891,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { // Assign new state immediately such that all getters return the right values, but use a // snapshot of the previous and new state so that listener invocations are triggered correctly. this.state = newState; + if (newState.hasPositionDiscontinuity || newState.newlyRenderedFirstFrame) { + // Clear one-time events to avoid signalling them again later. + this.state = + this.state + .buildUpon() + .clearPositionDiscontinuity() + .setNewlyRenderedFirstFrame(false) + .build(); + } boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady; boolean playbackStateChanged = previousState.playbackState != newState.playbackState; @@ -2917,7 +2926,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { PositionInfo positionInfo = getPositionInfo( newState, - /* useDiscontinuityPosition= */ state.hasPositionDiscontinuity, + /* useDiscontinuityPosition= */ newState.hasPositionDiscontinuity, window, period); listeners.queueEvent( @@ -2931,9 +2940,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { if (mediaItemTransitionReason != C.INDEX_UNSET) { @Nullable MediaItem mediaItem = - state.timeline.isEmpty() + newState.timeline.isEmpty() ? null - : state.playlist.get(state.currentMediaItemIndex).mediaItem; + : newState.playlist.get(state.currentMediaItemIndex).mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 65ac7a13bb..cb089090c8 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -1704,6 +1704,82 @@ public class SimpleBasePlayerTest { verify(listener, never()).onMediaItemTransition(any(), anyInt()); } + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void invalidateStateAndOtherOperation_withDiscontinuity_reportsDiscontinuityOnlyOnce() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0).build())) + .setPositionDiscontinuity( + Player.DISCONTINUITY_REASON_INTERNAL, /* discontinuityPositionMs= */ 2000) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handlePrepare() { + // We just care about the placeholder state, so return an unfulfilled future. + return SettableFuture.create(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.invalidateState(); + player.prepare(); + + // Assert listener calls (in particular getting only a single discontinuity). + verify(listener) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_INTERNAL)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); + verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener call. + @Test + public void + invalidateStateAndOtherOperation_withRenderedFirstFrame_reportsRenderedFirstFrameOnlyOnce() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0).build())) + .setNewlyRenderedFirstFrame(true) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handlePrepare() { + // We just care about the placeholder state, so return an unfulfilled future. + return SettableFuture.create(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.invalidateState(); + player.prepare(); + + // Assert listener calls (in particular getting only a single rendered first frame). + verify(listener).onRenderedFirstFrame(); + verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING); + verifyNoMoreInteractions(listener); + } + @Test public void invalidateState_duringAsyncMethodHandling_isIgnored() { State state1 = From 8e2692dc9c4dbff3d87f5e96fad893a2a949a149 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 14 Dec 2022 18:30:49 +0000 Subject: [PATCH 052/104] Allow unset index and position values + remove period index This simplifies some position tracking needs for an app implementing SimpleBasePlayer. - The period index can always be derived from the media item index and the position. So there is no need to set it separately. - The media item index can be left unset in the State in case the app doesn't care about the value or wants to set it the default start index (e.g. while the playlist is still empty where UNSET is different from zero). - Similarly, we should allow to set the content position (and buffered position) to C.TIME_UNSET to let the app ignore it or indicate the default position explictly. PiperOrigin-RevId: 495352633 (cherry picked from commit 91557ac9d4eaf83e68f563373541dc8e12043e00) --- .../android/exoplayer2/SimpleBasePlayer.java | 218 ++++++++++-------- .../exoplayer2/SimpleBasePlayerTest.java | 218 +++++++++++++++--- 2 files changed, 309 insertions(+), 127 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index ebf584dda0..d501170103 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -19,6 +19,7 @@ import static androidx.annotation.VisibleForTesting.PROTECTED; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.usToMs; import static java.lang.Math.max; @@ -130,12 +131,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { private Timeline timeline; private MediaMetadata playlistMetadata; private int currentMediaItemIndex; - private int currentPeriodIndex; private int currentAdGroupIndex; private int currentAdIndexInAdGroup; - private long contentPositionMs; + @Nullable private Long contentPositionMs; private PositionSupplier contentPositionMsSupplier; - private long adPositionMs; + @Nullable private Long adPositionMs; private PositionSupplier adPositionMsSupplier; private PositionSupplier contentBufferedPositionMsSupplier; private PositionSupplier adBufferedPositionMsSupplier; @@ -173,15 +173,14 @@ public abstract class SimpleBasePlayer extends BasePlayer { playlist = ImmutableList.of(); timeline = Timeline.EMPTY; playlistMetadata = MediaMetadata.EMPTY; - currentMediaItemIndex = 0; - currentPeriodIndex = C.INDEX_UNSET; + currentMediaItemIndex = C.INDEX_UNSET; currentAdGroupIndex = C.INDEX_UNSET; currentAdIndexInAdGroup = C.INDEX_UNSET; - contentPositionMs = C.TIME_UNSET; - contentPositionMsSupplier = PositionSupplier.ZERO; - adPositionMs = C.TIME_UNSET; + contentPositionMs = null; + contentPositionMsSupplier = PositionSupplier.getConstant(C.TIME_UNSET); + adPositionMs = null; adPositionMsSupplier = PositionSupplier.ZERO; - contentBufferedPositionMsSupplier = PositionSupplier.ZERO; + contentBufferedPositionMsSupplier = PositionSupplier.getConstant(C.TIME_UNSET); adBufferedPositionMsSupplier = PositionSupplier.ZERO; totalBufferedDurationMsSupplier = PositionSupplier.ZERO; hasPositionDiscontinuity = false; @@ -218,12 +217,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.timeline = state.timeline; this.playlistMetadata = state.playlistMetadata; this.currentMediaItemIndex = state.currentMediaItemIndex; - this.currentPeriodIndex = state.currentPeriodIndex; this.currentAdGroupIndex = state.currentAdGroupIndex; this.currentAdIndexInAdGroup = state.currentAdIndexInAdGroup; - this.contentPositionMs = C.TIME_UNSET; + this.contentPositionMs = null; this.contentPositionMsSupplier = state.contentPositionMsSupplier; - this.adPositionMs = C.TIME_UNSET; + this.adPositionMs = null; this.adPositionMsSupplier = state.adPositionMsSupplier; this.contentBufferedPositionMsSupplier = state.contentBufferedPositionMsSupplier; this.adBufferedPositionMsSupplier = state.adBufferedPositionMsSupplier; @@ -577,7 +575,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { *

      The media item index must be less than the number of {@linkplain #setPlaylist media * items in the playlist}, if set. * - * @param currentMediaItemIndex The current media item index. + * @param currentMediaItemIndex The current media item index, or {@link C#INDEX_UNSET} to + * assume the default first item in the playlist. * @return This builder. */ @CanIgnoreReturnValue @@ -586,26 +585,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { return this; } - /** - * Sets the current period index, or {@link C#INDEX_UNSET} to assume the first period of the - * current media item is played. - * - *

      The period index must be less than the total number of {@linkplain - * MediaItemData.Builder#setPeriods periods} in the media item, if set, and the period at the - * specified index must be part of the {@linkplain #setCurrentMediaItemIndex current media - * item}. - * - * @param currentPeriodIndex The current period index, or {@link C#INDEX_UNSET} to assume the - * first period of the current media item is played. - * @return This builder. - */ - @CanIgnoreReturnValue - public Builder setCurrentPeriodIndex(int currentPeriodIndex) { - checkArgument(currentPeriodIndex == C.INDEX_UNSET || currentPeriodIndex >= 0); - this.currentPeriodIndex = currentPeriodIndex; - return this; - } - /** * Sets the current ad indices, or {@link C#INDEX_UNSET} if no ad is playing. * @@ -635,7 +614,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { *

      This position will be converted to an advancing {@link PositionSupplier} if the overall * state indicates an advancing playback position. * - * @param positionMs The current content playback position in milliseconds. + *

      This method overrides any other {@link PositionSupplier} set via {@link + * #setContentPositionMs(PositionSupplier)}. + * + * @param positionMs The current content playback position in milliseconds, or {@link + * C#TIME_UNSET} to indicate the default start position. * @return This builder. */ @CanIgnoreReturnValue @@ -651,24 +634,30 @@ public abstract class SimpleBasePlayer extends BasePlayer { *

      The supplier is expected to return the updated position on every call if the playback is * advancing, for example by using {@link PositionSupplier#getExtrapolating}. * + *

      This method overrides any other position set via {@link #setContentPositionMs(long)}. + * * @param contentPositionMsSupplier The {@link PositionSupplier} for the current content - * playback position in milliseconds. + * playback position in milliseconds, or {@link C#TIME_UNSET} to indicate the default + * start position. * @return This builder. */ @CanIgnoreReturnValue public Builder setContentPositionMs(PositionSupplier contentPositionMsSupplier) { - this.contentPositionMs = C.TIME_UNSET; + this.contentPositionMs = null; this.contentPositionMsSupplier = contentPositionMsSupplier; return this; } /** - * Sets the current ad playback position in milliseconds. The * value is unused if no ad is + * Sets the current ad playback position in milliseconds. The value is unused if no ad is * playing. * *

      This position will be converted to an advancing {@link PositionSupplier} if the overall * state indicates an advancing ad playback position. * + *

      This method overrides any other {@link PositionSupplier} set via {@link + * #setAdPositionMs(PositionSupplier)}. + * * @param positionMs The current ad playback position in milliseconds. * @return This builder. */ @@ -685,13 +674,15 @@ public abstract class SimpleBasePlayer extends BasePlayer { *

      The supplier is expected to return the updated position on every call if the playback is * advancing, for example by using {@link PositionSupplier#getExtrapolating}. * + *

      This method overrides any other position set via {@link #setAdPositionMs(long)}. + * * @param adPositionMsSupplier The {@link PositionSupplier} for the current ad playback * position in milliseconds. The value is unused if no ad is playing. * @return This builder. */ @CanIgnoreReturnValue public Builder setAdPositionMs(PositionSupplier adPositionMsSupplier) { - this.adPositionMs = C.TIME_UNSET; + this.adPositionMs = null; this.adPositionMsSupplier = adPositionMsSupplier; return this; } @@ -701,7 +692,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { * playing content is buffered, in milliseconds. * * @param contentBufferedPositionMsSupplier The {@link PositionSupplier} for the estimated - * position up to which the currently playing content is buffered, in milliseconds. + * position up to which the currently playing content is buffered, in milliseconds, or + * {@link C#TIME_UNSET} to indicate the default start position. * @return This builder. */ @CanIgnoreReturnValue @@ -841,18 +833,19 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final Timeline timeline; /** The playlist {@link MediaMetadata}. */ public final MediaMetadata playlistMetadata; - /** The current media item index. */ - public final int currentMediaItemIndex; /** - * The current period index, or {@link C#INDEX_UNSET} to assume the first period of the current - * media item is played. + * The current media item index, or {@link C#INDEX_UNSET} to assume the default first item of + * the playlist is played. */ - public final int currentPeriodIndex; + public final int currentMediaItemIndex; /** The current ad group index, or {@link C#INDEX_UNSET} if no ad is playing. */ public final int currentAdGroupIndex; /** The current ad index in the ad group, or {@link C#INDEX_UNSET} if no ad is playing. */ public final int currentAdIndexInAdGroup; - /** The {@link PositionSupplier} for the current content playback position in milliseconds. */ + /** + * The {@link PositionSupplier} for the current content playback position in milliseconds, or + * {@link C#TIME_UNSET} to indicate the default start position. + */ public final PositionSupplier contentPositionMsSupplier; /** * The {@link PositionSupplier} for the current ad playback position in milliseconds. The value @@ -861,7 +854,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { public final PositionSupplier adPositionMsSupplier; /** * The {@link PositionSupplier} for the estimated position up to which the currently playing - * content is buffered, in milliseconds. + * content is buffered, in milliseconds, or {@link C#TIME_UNSET} to indicate the default start + * position. */ public final PositionSupplier contentBufferedPositionMsSupplier; /** @@ -890,22 +884,27 @@ public abstract class SimpleBasePlayer extends BasePlayer { checkArgument( builder.playbackState == Player.STATE_IDLE || builder.playbackState == Player.STATE_ENDED); + checkArgument( + builder.currentAdGroupIndex == C.INDEX_UNSET + && builder.currentAdIndexInAdGroup == C.INDEX_UNSET); } else { - checkArgument(builder.currentMediaItemIndex < builder.timeline.getWindowCount()); - if (builder.currentPeriodIndex != C.INDEX_UNSET) { - checkArgument(builder.currentPeriodIndex < builder.timeline.getPeriodCount()); - checkArgument( - builder.timeline.getPeriod(builder.currentPeriodIndex, new Timeline.Period()) - .windowIndex - == builder.currentMediaItemIndex); + int mediaItemIndex = builder.currentMediaItemIndex; + if (mediaItemIndex == C.INDEX_UNSET) { + mediaItemIndex = 0; // TODO: Use shuffle order to find first index. + } else { + checkArgument(builder.currentMediaItemIndex < builder.timeline.getWindowCount()); } if (builder.currentAdGroupIndex != C.INDEX_UNSET) { + Timeline.Period period = new Timeline.Period(); + Timeline.Window window = new Timeline.Window(); + long contentPositionMs = + builder.contentPositionMs != null + ? builder.contentPositionMs + : builder.contentPositionMsSupplier.get(); int periodIndex = - builder.currentPeriodIndex != C.INDEX_UNSET - ? builder.currentPeriodIndex - : builder.timeline.getWindow(builder.currentMediaItemIndex, new Timeline.Window()) - .firstPeriodIndex; - Timeline.Period period = builder.timeline.getPeriod(periodIndex, new Timeline.Period()); + getPeriodIndexFromWindowPosition( + builder.timeline, mediaItemIndex, contentPositionMs, window, period); + builder.timeline.getPeriod(periodIndex, period); checkArgument(builder.currentAdGroupIndex < period.getAdGroupCount()); int adCountInGroup = period.getAdCountInAdGroup(builder.currentAdGroupIndex); if (adCountInGroup != C.LENGTH_UNSET) { @@ -921,11 +920,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { checkArgument(!builder.isLoading); } PositionSupplier contentPositionMsSupplier = builder.contentPositionMsSupplier; - if (builder.contentPositionMs != C.TIME_UNSET) { + if (builder.contentPositionMs != null) { if (builder.currentAdGroupIndex == C.INDEX_UNSET && builder.playWhenReady && builder.playbackState == Player.STATE_READY - && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE) { + && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE + && builder.contentPositionMs != C.TIME_UNSET) { contentPositionMsSupplier = PositionSupplier.getExtrapolating( builder.contentPositionMs, builder.playbackParameters.speed); @@ -934,7 +934,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { } } PositionSupplier adPositionMsSupplier = builder.adPositionMsSupplier; - if (builder.adPositionMs != C.TIME_UNSET) { + if (builder.adPositionMs != null) { if (builder.currentAdGroupIndex != C.INDEX_UNSET && builder.playWhenReady && builder.playbackState == Player.STATE_READY @@ -973,7 +973,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { this.timeline = builder.timeline; this.playlistMetadata = builder.playlistMetadata; this.currentMediaItemIndex = builder.currentMediaItemIndex; - this.currentPeriodIndex = builder.currentPeriodIndex; this.currentAdGroupIndex = builder.currentAdGroupIndex; this.currentAdIndexInAdGroup = builder.currentAdIndexInAdGroup; this.contentPositionMsSupplier = contentPositionMsSupplier; @@ -1027,7 +1026,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { && playlist.equals(state.playlist) && playlistMetadata.equals(state.playlistMetadata) && currentMediaItemIndex == state.currentMediaItemIndex - && currentPeriodIndex == state.currentPeriodIndex && currentAdGroupIndex == state.currentAdGroupIndex && currentAdIndexInAdGroup == state.currentAdIndexInAdGroup && contentPositionMsSupplier.equals(state.contentPositionMsSupplier) @@ -1071,7 +1069,6 @@ public abstract class SimpleBasePlayer extends BasePlayer { result = 31 * result + playlist.hashCode(); result = 31 * result + playlistMetadata.hashCode(); result = 31 * result + currentMediaItemIndex; - result = 31 * result + currentPeriodIndex; result = 31 * result + currentAdGroupIndex; result = 31 * result + currentAdIndexInAdGroup; result = 31 * result + contentPositionMsSupplier.hashCode(); @@ -2201,7 +2198,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { .buildUpon() .setPlaybackState(Player.STATE_IDLE) .setTotalBufferedDurationMs(PositionSupplier.ZERO) - .setContentBufferedPositionMs(state.contentPositionMsSupplier) + .setContentBufferedPositionMs( + PositionSupplier.getConstant(getContentPositionMsInternal(state))) .setAdBufferedPositionMs(state.adPositionMsSupplier) .setIsLoading(false) .build()); @@ -2233,7 +2231,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { .buildUpon() .setPlaybackState(Player.STATE_IDLE) .setTotalBufferedDurationMs(PositionSupplier.ZERO) - .setContentBufferedPositionMs(state.contentPositionMsSupplier) + .setContentBufferedPositionMs( + PositionSupplier.getConstant(getContentPositionMsInternal(state))) .setAdBufferedPositionMs(state.adPositionMsSupplier) .setIsLoading(false) .build(); @@ -2300,13 +2299,13 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final int getCurrentPeriodIndex() { verifyApplicationThreadAndInitState(); - return getCurrentPeriodIndexInternal(state, window); + return getCurrentPeriodIndexInternal(state, window, period); } @Override public final int getCurrentMediaItemIndex() { verifyApplicationThreadAndInitState(); - return state.currentMediaItemIndex; + return getCurrentMediaItemIndexInternal(state); } @Override @@ -2362,14 +2361,13 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final long getContentPosition() { verifyApplicationThreadAndInitState(); - return state.contentPositionMsSupplier.get(); + return getContentPositionMsInternal(state); } @Override public final long getContentBufferedPosition() { verifyApplicationThreadAndInitState(); - return max( - state.contentBufferedPositionMsSupplier.get(), state.contentPositionMsSupplier.get()); + return max(getContentBufferedPositionMsInternal(state), getContentPositionMsInternal(state)); } @Override @@ -2942,7 +2940,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { MediaItem mediaItem = newState.timeline.isEmpty() ? null - : newState.playlist.get(state.currentMediaItemIndex).mediaItem; + : newState.playlist.get(getCurrentMediaItemIndexInternal(newState)).mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); @@ -3162,23 +3160,59 @@ public abstract class SimpleBasePlayer extends BasePlayer { private static Tracks getCurrentTracksInternal(State state) { return state.playlist.isEmpty() ? Tracks.EMPTY - : state.playlist.get(state.currentMediaItemIndex).tracks; + : state.playlist.get(getCurrentMediaItemIndexInternal(state)).tracks; } private static MediaMetadata getMediaMetadataInternal(State state) { return state.playlist.isEmpty() ? MediaMetadata.EMPTY - : state.playlist.get(state.currentMediaItemIndex).combinedMediaMetadata; + : state.playlist.get(getCurrentMediaItemIndexInternal(state)).combinedMediaMetadata; } - private static int getCurrentPeriodIndexInternal(State state, Timeline.Window window) { - if (state.currentPeriodIndex != C.INDEX_UNSET) { - return state.currentPeriodIndex; - } - if (state.timeline.isEmpty()) { + private static int getCurrentMediaItemIndexInternal(State state) { + if (state.currentMediaItemIndex != C.INDEX_UNSET) { return state.currentMediaItemIndex; } - return state.timeline.getWindow(state.currentMediaItemIndex, window).firstPeriodIndex; + return 0; // TODO: Use shuffle order to get first item if playlist is not empty. + } + + private static long getContentPositionMsInternal(State state) { + return getPositionOrDefaultInMediaItem(state.contentPositionMsSupplier.get(), state); + } + + private static long getContentBufferedPositionMsInternal(State state) { + return getPositionOrDefaultInMediaItem(state.contentBufferedPositionMsSupplier.get(), state); + } + + private static long getPositionOrDefaultInMediaItem(long positionMs, State state) { + if (positionMs != C.TIME_UNSET) { + return positionMs; + } + if (state.playlist.isEmpty()) { + return 0; + } + return usToMs(state.playlist.get(getCurrentMediaItemIndexInternal(state)).defaultPositionUs); + } + + private static int getCurrentPeriodIndexInternal( + State state, Timeline.Window window, Timeline.Period period) { + int currentMediaItemIndex = getCurrentMediaItemIndexInternal(state); + if (state.timeline.isEmpty()) { + return currentMediaItemIndex; + } + return getPeriodIndexFromWindowPosition( + state.timeline, currentMediaItemIndex, getContentPositionMsInternal(state), window, period); + } + + private static int getPeriodIndexFromWindowPosition( + Timeline timeline, + int windowIndex, + long windowPositionMs, + Timeline.Window window, + Timeline.Period period) { + Object periodUid = + timeline.getPeriodPositionUs(window, period, windowIndex, msToUs(windowPositionMs)).first; + return timeline.getIndexOfPeriod(periodUid); } private static @Player.TimelineChangeReason int getTimelineChangeReason( @@ -3209,9 +3243,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { return Player.DISCONTINUITY_REASON_REMOVE; } Object previousPeriodUid = - previousState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(previousState, window)); + previousState.timeline.getUidOfPeriod( + getCurrentPeriodIndexInternal(previousState, window, period)); Object newPeriodUid = - newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window)); + newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window, period)); if (!newPeriodUid.equals(previousPeriodUid) || previousState.currentAdGroupIndex != newState.currentAdGroupIndex || previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) { @@ -3247,7 +3282,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { State state, Object currentPeriodUid, Timeline.Period period) { return state.currentAdGroupIndex != C.INDEX_UNSET ? state.adPositionMsSupplier.get() - : state.contentPositionMsSupplier.get() + : getContentPositionMsInternal(state) - state.timeline.getPeriodByUid(currentPeriodUid, period).getPositionInWindowMs(); } @@ -3268,11 +3303,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { Timeline.Period period) { @Nullable Object windowUid = null; @Nullable Object periodUid = null; - int mediaItemIndex = state.currentMediaItemIndex; + int mediaItemIndex = getCurrentMediaItemIndexInternal(state); int periodIndex = C.INDEX_UNSET; @Nullable MediaItem mediaItem = null; if (!state.timeline.isEmpty()) { - periodIndex = getCurrentPeriodIndexInternal(state, window); + periodIndex = getCurrentPeriodIndexInternal(state, window, period); periodUid = state.timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; windowUid = state.timeline.getWindow(mediaItemIndex, window).uid; mediaItem = window.mediaItem; @@ -3284,9 +3319,9 @@ public abstract class SimpleBasePlayer extends BasePlayer { contentPositionMs = state.currentAdGroupIndex == C.INDEX_UNSET ? positionMs - : state.contentPositionMsSupplier.get(); + : getContentPositionMsInternal(state); } else { - contentPositionMs = state.contentPositionMsSupplier.get(); + contentPositionMs = getContentPositionMsInternal(state); positionMs = state.currentAdGroupIndex != C.INDEX_UNSET ? state.adPositionMsSupplier.get() @@ -3317,8 +3352,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { return MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; } Object previousWindowUid = - previousState.timeline.getWindow(previousState.currentMediaItemIndex, window).uid; - Object newWindowUid = newState.timeline.getWindow(newState.currentMediaItemIndex, window).uid; + previousState.timeline.getWindow(getCurrentMediaItemIndexInternal(previousState), window) + .uid; + Object newWindowUid = + newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window).uid; if (!previousWindowUid.equals(newWindowUid)) { if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { return MEDIA_ITEM_TRANSITION_REASON_AUTO; @@ -3331,8 +3368,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { // Only mark changes within the current item as a transition if we are repeating automatically // or via a seek to next/previous. if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION - && previousState.contentPositionMsSupplier.get() - > newState.contentPositionMsSupplier.get()) { + && getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) { return MEDIA_ITEM_TRANSITION_REASON_REPEAT; } if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index cb089090c8..3cb6f57de4 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -135,7 +135,6 @@ public class SimpleBasePlayerTest { .build())) .setPlaylistMetadata(new MediaMetadata.Builder().setArtist("artist").build()) .setCurrentMediaItemIndex(1) - .setCurrentPeriodIndex(1) .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) .setContentPositionMs(() -> 456) .setAdPositionMs(() -> 6678) @@ -282,7 +281,6 @@ public class SimpleBasePlayerTest { .setPlaylist(playlist) .setPlaylistMetadata(playlistMetadata) .setCurrentMediaItemIndex(1) - .setCurrentPeriodIndex(1) .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2) .setContentPositionMs(contentPositionSupplier) .setAdPositionMs(adPositionSupplier) @@ -322,7 +320,6 @@ public class SimpleBasePlayerTest { assertThat(state.playlist).isEqualTo(playlist); assertThat(state.playlistMetadata).isEqualTo(playlistMetadata); assertThat(state.currentMediaItemIndex).isEqualTo(1); - assertThat(state.currentPeriodIndex).isEqualTo(1); assertThat(state.currentAdGroupIndex).isEqualTo(1); assertThat(state.currentAdIndexInAdGroup).isEqualTo(2); assertThat(state.contentPositionMsSupplier).isEqualTo(contentPositionSupplier); @@ -369,7 +366,32 @@ public class SimpleBasePlayerTest { } @Test - public void stateBuilderBuild_currentWindowIndexExceedsPlaylistLength_throwsException() { + public void stateBuilderBuild_currentMediaItemIndexUnset_doesNotThrow() { + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .setCurrentMediaItemIndex(C.INDEX_UNSET) + .build(); + + assertThat(state.currentMediaItemIndex).isEqualTo(C.INDEX_UNSET); + } + + @Test + public void stateBuilderBuild_currentMediaItemIndexSetForEmptyPlaylist_doesNotThrow() { + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist(ImmutableList.of()) + .setCurrentMediaItemIndex(20) + .build(); + + assertThat(state.currentMediaItemIndex).isEqualTo(20); + } + + @Test + public void stateBuilderBuild_currentMediaItemIndexExceedsPlaylistLength_throwsException() { assertThrows( IllegalArgumentException.class, () -> @@ -383,37 +405,6 @@ public class SimpleBasePlayerTest { .build()); } - @Test - public void stateBuilderBuild_currentPeriodIndexExceedsPlaylistLength_throwsException() { - assertThrows( - IllegalArgumentException.class, - () -> - new SimpleBasePlayer.State.Builder() - .setPlaylist( - ImmutableList.of( - new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) - .build())) - .setCurrentPeriodIndex(2) - .build()); - } - - @Test - public void stateBuilderBuild_currentPeriodIndexInOtherMediaItem_throwsException() { - assertThrows( - IllegalArgumentException.class, - () -> - new SimpleBasePlayer.State.Builder() - .setPlaylist( - ImmutableList.of( - new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(), - new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()) - .build())) - .setCurrentMediaItemIndex(0) - .setCurrentPeriodIndex(1) - .build()); - } - @Test public void stateBuilderBuild_currentAdGroupIndexExceedsAdGroupCount_throwsException() { assertThrows( @@ -460,6 +451,16 @@ public class SimpleBasePlayerTest { .build()); } + @Test + public void stateBuilderBuild_setAdAndEmptyPlaylist_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> + new SimpleBasePlayer.State.Builder() + .setCurrentAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 3) + .build()); + } + @Test public void stateBuilderBuild_playerErrorInNonIdleState_throwsException() { assertThrows( @@ -541,6 +542,27 @@ public class SimpleBasePlayerTest { assertThat(position2).isEqualTo(8000); } + @Test + public void stateBuilderBuild_withUnsetPositionAndPlaying_returnsConstantContentPosition() { + SystemClock.setCurrentTimeMillis(10000); + + SimpleBasePlayer.State state = + new SimpleBasePlayer.State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build())) + .setContentPositionMs(C.TIME_UNSET) + .setPlayWhenReady(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) + .setPlaybackState(Player.STATE_READY) + .build(); + long position1 = state.contentPositionMsSupplier.get(); + SystemClock.setCurrentTimeMillis(12000); + long position2 = state.contentPositionMsSupplier.get(); + + assertThat(position1).isEqualTo(C.TIME_UNSET); + assertThat(position2).isEqualTo(C.TIME_UNSET); + } + @Test public void stateBuilderBuild_returnsConstantContentPositionWhenNotPlaying() { SystemClock.setCurrentTimeMillis(10000); @@ -872,7 +894,6 @@ public class SimpleBasePlayerTest { .setPlaylist(playlist) .setPlaylistMetadata(playlistMetadata) .setCurrentMediaItemIndex(1) - .setCurrentPeriodIndex(1) .setContentPositionMs(contentPositionSupplier) .setContentBufferedPositionMs(contentBufferedPositionSupplier) .setTotalBufferedDurationMs(totalBufferedPositionSupplier) @@ -1056,6 +1077,131 @@ public class SimpleBasePlayerTest { assertThat(player.getCurrentMediaItemIndex()).isEqualTo(4); } + @Test + public void getCurrentMediaItemIndex_withUnsetIndexInState_returnsDefaultIndex() { + State state = new State.Builder().setCurrentMediaItemIndex(C.INDEX_UNSET).build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + } + + @Test + public void getCurrentPeriodIndex_withUnsetIndexInState_returnsPeriodForCurrentPosition() { + State state = + new State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setPeriods( + ImmutableList.of( + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ "period0") + .setDurationUs(60_000_000) + .build(), + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ "period1") + .setDurationUs(5_000_000) + .build(), + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ "period2") + .setDurationUs(5_000_000) + .build())) + .setPositionInFirstPeriodUs(50_000_000) + .build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(12_000) + .build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getCurrentPeriodIndex()).isEqualTo(2); + } + + @Test + public void getCurrentPosition_withUnsetPositionInState_returnsDefaultPosition() { + State state = + new State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0) + .setDefaultPositionUs(5_000_000) + .build())) + .setContentPositionMs(C.TIME_UNSET) + .build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getCurrentPosition()).isEqualTo(5000); + } + + @Test + public void getBufferedPosition_withUnsetBufferedPositionInState_returnsDefaultPosition() { + State state = + new State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0) + .setDefaultPositionUs(5_000_000) + .build())) + .setContentBufferedPositionMs( + SimpleBasePlayer.PositionSupplier.getConstant(C.TIME_UNSET)) + .build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getBufferedPosition()).isEqualTo(5000); + } + + @Test + public void + getBufferedPosition_withUnsetBufferedPositionAndPositionInState_returnsDefaultPosition() { + State state = + new State.Builder() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0) + .setDefaultPositionUs(5_000_000) + .build())) + .setContentPositionMs(C.TIME_UNSET) + .setContentBufferedPositionMs( + SimpleBasePlayer.PositionSupplier.getConstant(C.TIME_UNSET)) + .build(); + + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + }; + + assertThat(player.getBufferedPosition()).isEqualTo(5000); + } + @SuppressWarnings("deprecation") // Verifying deprecated listener call. @Test public void invalidateState_updatesStateAndInformsListeners() throws Exception { From b9fd7fd431d01cf1b4ed618219c4187e4d89d0a4 Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 15 Dec 2022 17:23:27 +0000 Subject: [PATCH 053/104] Remove parameters with `null` values from bundle in `MediaMetadata` Improves the time taken to construct `playerInfo` from its bundle from ~450 ms to ~400 ms. Each `MediaItem` inside `Timeline.Window` contains `MediaMetadata` and hence is a good candidate for bundling optimisations. There already exists a test to check all parameters for null values when unset. PiperOrigin-RevId: 495614719 (cherry picked from commit 8dea624c980ff65aece63c6aeecdeef894470231) --- .../android/exoplayer2/MediaMetadata.java | 61 ++++++++++++++----- .../android/exoplayer2/MediaMetadataTest.java | 22 +++++-- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 102f26b00c..72cd709204 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -1174,22 +1174,51 @@ public final class MediaMetadata implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putCharSequence(keyForField(FIELD_TITLE), title); - bundle.putCharSequence(keyForField(FIELD_ARTIST), artist); - bundle.putCharSequence(keyForField(FIELD_ALBUM_TITLE), albumTitle); - bundle.putCharSequence(keyForField(FIELD_ALBUM_ARTIST), albumArtist); - bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); - bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); - bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); - bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); - bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); - bundle.putCharSequence(keyForField(FIELD_WRITER), writer); - bundle.putCharSequence(keyForField(FIELD_COMPOSER), composer); - bundle.putCharSequence(keyForField(FIELD_CONDUCTOR), conductor); - bundle.putCharSequence(keyForField(FIELD_GENRE), genre); - bundle.putCharSequence(keyForField(FIELD_COMPILATION), compilation); - bundle.putCharSequence(keyForField(FIELD_STATION), station); - + if (title != null) { + bundle.putCharSequence(keyForField(FIELD_TITLE), title); + } + if (artist != null) { + bundle.putCharSequence(keyForField(FIELD_ARTIST), artist); + } + if (albumTitle != null) { + bundle.putCharSequence(keyForField(FIELD_ALBUM_TITLE), albumTitle); + } + if (albumArtist != null) { + bundle.putCharSequence(keyForField(FIELD_ALBUM_ARTIST), albumArtist); + } + if (displayTitle != null) { + bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); + } + if (subtitle != null) { + bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); + } + if (description != null) { + bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); + } + if (artworkData != null) { + bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); + } + if (artworkUri != null) { + bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); + } + if (writer != null) { + bundle.putCharSequence(keyForField(FIELD_WRITER), writer); + } + if (composer != null) { + bundle.putCharSequence(keyForField(FIELD_COMPOSER), composer); + } + if (conductor != null) { + bundle.putCharSequence(keyForField(FIELD_CONDUCTOR), conductor); + } + if (genre != null) { + bundle.putCharSequence(keyForField(FIELD_GENRE), genre); + } + if (compilation != null) { + bundle.putCharSequence(keyForField(FIELD_COMPILATION), compilation); + } + if (station != null) { + bundle.putCharSequence(keyForField(FIELD_STATION), station); + } if (userRating != null) { bundle.putBundle(keyForField(FIELD_USER_RATING), userRating.toBundle()); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 2e1fd383c6..8b314fdb81 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -107,13 +107,27 @@ public class MediaMetadataTest { } @Test - public void roundTripViaBundle_yieldsEqualInstance() { + public void createMinimalMediaMetadata_roundTripViaBundle_yieldsEqualInstance() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().build(); + + MediaMetadata mediaMetadataFromBundle = + MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); + + assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata); + // Extras is not implemented in MediaMetadata.equals(Object o). + assertThat(mediaMetadataFromBundle.extras).isNull(); + } + + @Test + public void createFullyPopulatedMediaMetadata_roundTripViaBundle_yieldsEqualInstance() { MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata(); - MediaMetadata fromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); - assertThat(fromBundle).isEqualTo(mediaMetadata); + MediaMetadata mediaMetadataFromBundle = + MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); + + assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata); // Extras is not implemented in MediaMetadata.equals(Object o). - assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); + assertThat(mediaMetadataFromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); } @Test From 5ff90692b601b6f66c4a86c70a81739a046acd02 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 15 Dec 2022 19:00:04 +0000 Subject: [PATCH 054/104] Use theme when loading drawables on API 21+ Issue: androidx/media#220 PiperOrigin-RevId: 495642588 (cherry picked from commit 33f8f406929c739e98bbea475b3609beb7db2b5f) --- .../google/android/exoplayer2/util/Util.java | 27 +++++++++ .../exoplayer2/ui/PlayerControlView.java | 11 ++-- .../android/exoplayer2/ui/PlayerView.java | 14 +++-- .../ui/StyledPlayerControlView.java | 58 ++++++++++++------- .../exoplayer2/ui/StyledPlayerView.java | 14 +++-- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 315ffacc98..017cb525b4 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -47,6 +47,7 @@ import android.content.res.Resources; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; +import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.media.AudioFormat; import android.media.AudioManager; @@ -66,6 +67,8 @@ import android.util.SparseLongArray; import android.view.Display; import android.view.SurfaceView; import android.view.WindowManager; +import androidx.annotation.DoNotInline; +import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; @@ -2744,6 +2747,22 @@ public final class Util { return sum; } + /** + * Returns a {@link Drawable} for the given resource or throws a {@link + * Resources.NotFoundException} if not found. + * + * @param context The context to get the theme from starting with API 21. + * @param resources The resources to load the drawable from. + * @param drawableRes The drawable resource int. + * @return The loaded {@link Drawable}. + */ + public static Drawable getDrawable( + Context context, Resources resources, @DrawableRes int drawableRes) { + return SDK_INT >= 21 + ? Api21.getDrawable(context, resources, drawableRes) + : resources.getDrawable(drawableRes); + } + @Nullable private static String getSystemProperty(String name) { try { @@ -2980,4 +2999,12 @@ public final class Util { 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; + + @RequiresApi(21) + private static final class Api21 { + @DoNotInline + public static Drawable getDrawable(Context context, Resources resources, @DrawableRes int res) { + return resources.getDrawable(res, context.getTheme()); + } + } } 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 e880b0f254..b36cac1857 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 @@ -28,6 +28,7 @@ import static com.google.android.exoplayer2.Player.EVENT_POSITION_DISCONTINUITY; import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED; +import static com.google.android.exoplayer2.util.Util.getDrawable; import android.annotation.SuppressLint; import android.content.Context; @@ -488,11 +489,11 @@ public class PlayerControlView extends FrameLayout { buttonAlphaDisabled = (float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_disabled) / 100; - repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_off); - repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_one); - repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_all); - shuffleOnButtonDrawable = resources.getDrawable(R.drawable.exo_controls_shuffle_on); - shuffleOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_shuffle_off); + repeatOffButtonDrawable = getDrawable(context, resources, R.drawable.exo_controls_repeat_off); + repeatOneButtonDrawable = getDrawable(context, resources, R.drawable.exo_controls_repeat_one); + repeatAllButtonDrawable = getDrawable(context, resources, R.drawable.exo_controls_repeat_all); + shuffleOnButtonDrawable = getDrawable(context, resources, R.drawable.exo_controls_shuffle_on); + shuffleOffButtonDrawable = getDrawable(context, resources, R.drawable.exo_controls_shuffle_off); repeatOffButtonContentDescription = resources.getString(R.string.exo_controls_repeat_off_description); repeatOneButtonContentDescription = 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 9824d984a0..504a6c0250 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 @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ui; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; +import static com.google.android.exoplayer2.util.Util.getDrawable; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; @@ -342,9 +343,9 @@ public class PlayerView extends FrameLayout implements AdViewProvider { overlayFrameLayout = null; ImageView logo = new ImageView(context); if (Util.SDK_INT >= 23) { - configureEditModeLogoV23(getResources(), logo); + configureEditModeLogoV23(context, getResources(), logo); } else { - configureEditModeLogo(getResources(), logo); + configureEditModeLogo(context, getResources(), logo); } addView(logo); return; @@ -1388,13 +1389,14 @@ public class PlayerView extends FrameLayout implements AdViewProvider { } @RequiresApi(23) - private static void configureEditModeLogoV23(Resources resources, ImageView logo) { - logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); + private static void configureEditModeLogoV23( + Context context, Resources resources, ImageView logo) { + logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); } - private static void configureEditModeLogo(Resources resources, ImageView logo) { - logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo)); + private static void configureEditModeLogo(Context context, Resources resources, ImageView logo) { + logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index f277a99ba8..e8d72983f0 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -34,6 +34,7 @@ import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TRACKS_CHANGED; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.android.exoplayer2.util.Util.getDrawable; import android.annotation.SuppressLint; import android.content.Context; @@ -53,7 +54,9 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.PopupWindow; import android.widget.TextView; +import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -535,11 +538,11 @@ public class StyledPlayerControlView extends FrameLayout { settingTexts[SETTINGS_PLAYBACK_SPEED_POSITION] = resources.getString(R.string.exo_controls_playback_speed); settingIcons[SETTINGS_PLAYBACK_SPEED_POSITION] = - resources.getDrawable(R.drawable.exo_styled_controls_speed); + getDrawable(context, resources, R.drawable.exo_styled_controls_speed); settingTexts[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] = resources.getString(R.string.exo_track_selection_title_audio); settingIcons[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] = - resources.getDrawable(R.drawable.exo_styled_controls_audiotrack); + getDrawable(context, resources, R.drawable.exo_styled_controls_audiotrack); settingsAdapter = new SettingsAdapter(settingTexts, settingIcons); settingsWindowMargin = resources.getDimensionPixelSize(R.dimen.exo_settings_offset); settingsView = @@ -559,8 +562,10 @@ public class StyledPlayerControlView extends FrameLayout { needToHideBars = true; trackNameProvider = new DefaultTrackNameProvider(getResources()); - subtitleOnButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_subtitle_on); - subtitleOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_subtitle_off); + subtitleOnButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_subtitle_on); + subtitleOffButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_subtitle_off); subtitleOnContentDescription = resources.getString(R.string.exo_controls_cc_enabled_description); subtitleOffContentDescription = @@ -571,14 +576,20 @@ public class StyledPlayerControlView extends FrameLayout { new PlaybackSpeedAdapter( resources.getStringArray(R.array.exo_controls_playback_speeds), PLAYBACK_SPEEDS); - fullScreenExitDrawable = resources.getDrawable(R.drawable.exo_styled_controls_fullscreen_exit); + fullScreenExitDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_fullscreen_exit); fullScreenEnterDrawable = - resources.getDrawable(R.drawable.exo_styled_controls_fullscreen_enter); - repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_off); - repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_one); - repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_all); - shuffleOnButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_shuffle_on); - shuffleOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_shuffle_off); + getDrawable(context, resources, R.drawable.exo_styled_controls_fullscreen_enter); + repeatOffButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_off); + repeatOneButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_one); + repeatAllButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_all); + shuffleOnButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_shuffle_on); + shuffleOffButtonDrawable = + getDrawable(context, resources, R.drawable.exo_styled_controls_shuffle_off); fullScreenExitContentDescription = resources.getString(R.string.exo_controls_fullscreen_exit_description); fullScreenEnterContentDescription = @@ -961,17 +972,20 @@ public class StyledPlayerControlView extends FrameLayout { return; } if (playPauseButton != null) { - if (shouldShowPauseButton()) { - ((ImageView) playPauseButton) - .setImageDrawable(resources.getDrawable(R.drawable.exo_styled_controls_pause)); - playPauseButton.setContentDescription( - resources.getString(R.string.exo_controls_pause_description)); - } else { - ((ImageView) playPauseButton) - .setImageDrawable(resources.getDrawable(R.drawable.exo_styled_controls_play)); - playPauseButton.setContentDescription( - resources.getString(R.string.exo_controls_play_description)); - } + boolean shouldShowPauseButton = shouldShowPauseButton(); + @DrawableRes + int drawableRes = + shouldShowPauseButton + ? R.drawable.exo_styled_controls_pause + : R.drawable.exo_styled_controls_play; + @StringRes + int stringRes = + shouldShowPauseButton + ? R.string.exo_controls_pause_description + : R.string.exo_controls_play_description; + ((ImageView) playPauseButton) + .setImageDrawable(getDrawable(getContext(), resources, drawableRes)); + playPauseButton.setContentDescription(resources.getString(stringRes)); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index da17c4c1c7..33b95c087b 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ui; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.getDrawable; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; @@ -287,9 +288,9 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { overlayFrameLayout = null; ImageView logo = new ImageView(context); if (Util.SDK_INT >= 23) { - configureEditModeLogoV23(getResources(), logo); + configureEditModeLogoV23(context, getResources(), logo); } else { - configureEditModeLogo(getResources(), logo); + configureEditModeLogo(context, getResources(), logo); } addView(logo); return; @@ -1415,13 +1416,14 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { } @RequiresApi(23) - private static void configureEditModeLogoV23(Resources resources, ImageView logo) { - logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); + private static void configureEditModeLogoV23( + Context context, Resources resources, ImageView logo) { + logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); } - private static void configureEditModeLogo(Resources resources, ImageView logo) { - logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo)); + private static void configureEditModeLogo(Context context, Resources resources, ImageView logo) { + logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); } From 4b7a00534049c18914f5ec924ae6f37fbb0a9d36 Mon Sep 17 00:00:00 2001 From: rohks Date: Fri, 16 Dec 2022 12:41:29 +0000 Subject: [PATCH 055/104] Rename `EMPTY_MEDIA_ITEM` to `PLACEHOLDER_MEDIA_ITEM` The `MediaItem` instances in the following cases are not actually empty but acts as a placeholder. `EMPTY_MEDIA_ITEM` can also be confused with `MediaItem.EMPTY`. PiperOrigin-RevId: 495843012 (cherry picked from commit 74559b4a188e476378f3f0df908598ce763d909a) --- .../main/java/com/google/android/exoplayer2/Timeline.java | 6 +++--- .../android/exoplayer2/source/ConcatenatingMediaSource.java | 6 +++--- .../android/exoplayer2/source/MergingMediaSource.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 6b958bf426..086cd0b6ef 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -158,7 +158,7 @@ public abstract class Timeline implements Bundleable { private static final Object FAKE_WINDOW_UID = new Object(); - private static final MediaItem EMPTY_MEDIA_ITEM = + private static final MediaItem PLACEHOLDER_MEDIA_ITEM = new MediaItem.Builder() .setMediaId("com.google.android.exoplayer2.Timeline") .setUri(Uri.EMPTY) @@ -258,7 +258,7 @@ public abstract class Timeline implements Bundleable { /** Creates window. */ public Window() { uid = SINGLE_WINDOW_UID; - mediaItem = EMPTY_MEDIA_ITEM; + mediaItem = PLACEHOLDER_MEDIA_ITEM; } /** Sets the data held by this window. */ @@ -280,7 +280,7 @@ public abstract class Timeline implements Bundleable { int lastPeriodIndex, long positionInFirstPeriodUs) { this.uid = uid; - this.mediaItem = mediaItem != null ? mediaItem : EMPTY_MEDIA_ITEM; + this.mediaItem = mediaItem != null ? mediaItem : PLACEHOLDER_MEDIA_ITEM; this.tag = mediaItem != null && mediaItem.localConfiguration != null ? mediaItem.localConfiguration.tag 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 60548f930e..eb6226874b 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 @@ -59,7 +59,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource { } private static final int PERIOD_COUNT_UNSET = -1; - private static final MediaItem EMPTY_MEDIA_ITEM = + private static final MediaItem PLACEHOLDER_MEDIA_ITEM = new MediaItem.Builder().setMediaId("MergingMediaSource").build(); private final boolean adjustPeriodTimeOffsets; @@ -161,7 +161,7 @@ public final class MergingMediaSource extends CompositeMediaSource { @Override public MediaItem getMediaItem() { - return mediaSources.length > 0 ? mediaSources[0].getMediaItem() : EMPTY_MEDIA_ITEM; + return mediaSources.length > 0 ? mediaSources[0].getMediaItem() : PLACEHOLDER_MEDIA_ITEM; } @Override From f1784862e0bb36d99eb9ecf2738f2978edd869ea Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 16 Dec 2022 14:10:28 +0000 Subject: [PATCH 056/104] Clarify behavior for out-of-bounds indices and align implementations Some Player methods operate relative to existing indices in the playlist (add,remove,move,seek). As these operations may be issued from a place with a stale playlist (e.g. a controller that sends a command while the playlist is changing), we have to handle out- of-bounds indices gracefully. In most cases this is already documented and implemented correctly. However, some cases are not documented and the existing player implementations don't handle these cases consistently (or in some cases not even correctly). PiperOrigin-RevId: 495856295 (cherry picked from commit a1c0b10482baffc3721561446ee4bb788d8a1231) --- .../exoplayer2/ext/cast/CastPlayer.java | 27 ++-- .../com/google/android/exoplayer2/Player.java | 27 ++-- .../android/exoplayer2/ExoPlayerImpl.java | 38 +++--- .../android/exoplayer2/ExoPlayerTest.java | 119 ++++++++++++++---- 4 files changed, 145 insertions(+), 66 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 a87fe9a437..02ab698a0f 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 @@ -43,7 +43,6 @@ import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; @@ -294,7 +293,7 @@ public final class CastPlayer extends BasePlayer { @Override public void addMediaItems(int index, List mediaItems) { - Assertions.checkArgument(index >= 0); + checkArgument(index >= 0); int uid = MediaQueueItem.INVALID_ITEM_ID; if (index < currentTimeline.getWindowCount()) { uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid; @@ -304,14 +303,11 @@ public final class CastPlayer extends BasePlayer { @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { - Assertions.checkArgument( - fromIndex >= 0 - && fromIndex <= toIndex - && toIndex <= currentTimeline.getWindowCount() - && newIndex >= 0 - && newIndex < currentTimeline.getWindowCount()); - newIndex = min(newIndex, currentTimeline.getWindowCount() - (toIndex - fromIndex)); - if (fromIndex == toIndex || fromIndex == newIndex) { + checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newIndex >= 0); + int playlistSize = currentTimeline.getWindowCount(); + toIndex = min(toIndex, playlistSize); + newIndex = min(newIndex, playlistSize - (toIndex - fromIndex)); + if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newIndex) { // Do nothing. return; } @@ -324,9 +320,10 @@ public final class CastPlayer extends BasePlayer { @Override public void removeMediaItems(int fromIndex, int toIndex) { - Assertions.checkArgument(fromIndex >= 0 && toIndex >= fromIndex); - toIndex = min(toIndex, currentTimeline.getWindowCount()); - if (fromIndex == toIndex) { + checkArgument(fromIndex >= 0 && toIndex >= fromIndex); + int playlistSize = currentTimeline.getWindowCount(); + toIndex = min(toIndex, playlistSize); + if (fromIndex >= playlistSize || fromIndex == toIndex) { // Do nothing. return; } @@ -404,6 +401,10 @@ public final class CastPlayer extends BasePlayer { long positionMs, @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { + checkArgument(mediaItemIndex >= 0); + if (!currentTimeline.isEmpty() && mediaItemIndex >= currentTimeline.getWindowCount()) { + return; + } MediaStatus mediaStatus = getMediaStatus(); // We assume the default position is 0. There is no support for seeking to the default position // in RemoteMediaClient. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 3272106ee8..0bc6e543ac 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -1655,7 +1655,8 @@ public interface Player { /** * Moves the media item at the current index to the new index. * - * @param currentIndex The current index of the media item to move. + * @param currentIndex The current index of the media item to move. If the index is larger than + * the size of the playlist, the request is ignored. * @param newIndex The new index of the media item. If the new index is larger than the size of * the playlist the item is moved to the end of the playlist. */ @@ -1664,8 +1665,10 @@ public interface Player { /** * Moves the media item range to the new index. * - * @param fromIndex The start of the range to move. - * @param toIndex The first item not to be included in the range (exclusive). + * @param fromIndex The start of the range to move. If the index is larger than the size of the + * playlist, the request is ignored. + * @param toIndex The first item not to be included in the range (exclusive). If the index is + * larger than the size of the playlist, items up to the end of the playlist are moved. * @param newIndex The new index of the first media item of the range. If the new index is larger * than the size of the remaining playlist after removing the range, the range is moved to the * end of the playlist. @@ -1675,16 +1678,18 @@ public interface Player { /** * Removes the media item at the given index of the playlist. * - * @param index The index at which to remove the media item. + * @param index The index at which to remove the media item. If the index is larger than the size + * of the playlist, the request is ignored. */ void removeMediaItem(int index); /** * Removes a range of media items from the playlist. * - * @param fromIndex The index at which to start removing media items. + * @param fromIndex The index at which to start removing media items. If the index is larger than + * the size of the playlist, the request is ignored. * @param toIndex The index of the first item to be kept (exclusive). If the index is larger than - * the size of the playlist, media items to the end of the playlist are removed. + * the size of the playlist, media items up to the end of the playlist are removed. */ void removeMediaItems(int fromIndex, int toIndex); @@ -1865,9 +1870,8 @@ public interface Player { * For other streams it will typically be the start. * * @param mediaItemIndex The index of the {@link MediaItem} whose associated default position - * should be seeked to. - * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided - * {@code mediaItemIndex} is not within the bounds of the current timeline. + * should be seeked to. If the index is larger than the size of the playlist, the request is + * ignored. */ void seekToDefaultPosition(int mediaItemIndex); @@ -1882,11 +1886,10 @@ public interface Player { /** * Seeks to a position specified in milliseconds in the specified {@link MediaItem}. * - * @param mediaItemIndex The index of the {@link MediaItem}. + * @param mediaItemIndex The index of the {@link MediaItem}. If the index is larger than the size + * of the playlist, the request is ignored. * @param positionMs The seek position in the specified {@link MediaItem}, or {@link C#TIME_UNSET} * to seek to the media item's default position. - * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided - * {@code mediaItemIndex} is not within the bounds of the current timeline. */ void seekTo(int mediaItemIndex, long positionMs); 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 2adf381621..18d90c9f17 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 @@ -29,6 +29,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLE import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -82,7 +83,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.HandlerWrapper; @@ -612,7 +612,6 @@ import java.util.concurrent.TimeoutException; @Override public void addMediaItems(int index, List mediaItems) { verifyApplicationThread(); - index = min(index, mediaSourceHolderSnapshots.size()); addMediaSources(index, createMediaSources(mediaItems)); } @@ -637,7 +636,8 @@ import java.util.concurrent.TimeoutException; @Override public void addMediaSources(int index, List mediaSources) { verifyApplicationThread(); - Assertions.checkArgument(index >= 0); + checkArgument(index >= 0); + index = min(index, mediaSourceHolderSnapshots.size()); Timeline oldTimeline = getCurrentTimeline(); pendingOperationAcks++; List holders = addMediaSourceHolders(index, mediaSources); @@ -663,7 +663,13 @@ import java.util.concurrent.TimeoutException; @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); - toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); + checkArgument(fromIndex >= 0 && toIndex >= fromIndex); + int playlistSize = mediaSourceHolderSnapshots.size(); + toIndex = min(toIndex, playlistSize); + if (fromIndex >= playlistSize || fromIndex == toIndex) { + // Do nothing. + return; + } PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex); boolean positionDiscontinuity = !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); @@ -682,14 +688,16 @@ import java.util.concurrent.TimeoutException; @Override public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { verifyApplicationThread(); - Assertions.checkArgument( - fromIndex >= 0 - && fromIndex <= toIndex - && toIndex <= mediaSourceHolderSnapshots.size() - && newFromIndex >= 0); + checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newFromIndex >= 0); + int playlistSize = mediaSourceHolderSnapshots.size(); + toIndex = min(toIndex, playlistSize); + newFromIndex = min(newFromIndex, playlistSize - (toIndex - fromIndex)); + if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newFromIndex) { + // Do nothing. + return; + } Timeline oldTimeline = getCurrentTimeline(); pendingOperationAcks++; - newFromIndex = min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex)); Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = @@ -818,11 +826,11 @@ import java.util.concurrent.TimeoutException; @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { verifyApplicationThread(); + checkArgument(mediaItemIndex >= 0); analyticsCollector.notifySeekStarted(); Timeline timeline = playbackInfo.timeline; - if (mediaItemIndex < 0 - || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs); + if (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount()) { + return; } pendingOperationAcks++; if (isPlayingAd()) { @@ -2250,8 +2258,6 @@ import java.util.concurrent.TimeoutException; } private PlaybackInfo removeMediaItemsInternal(int fromIndex, int toIndex) { - Assertions.checkArgument( - fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size()); int currentIndex = getCurrentMediaItemIndex(); Timeline oldTimeline = getCurrentTimeline(); int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); @@ -2290,7 +2296,7 @@ import java.util.concurrent.TimeoutException; private PlaybackInfo maskTimelineAndPosition( PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair periodPositionUs) { - Assertions.checkArgument(timeline.isEmpty() || periodPositionUs != null); + checkArgument(timeline.isEmpty() || periodPositionUs != null); Timeline oldTimeline = playbackInfo.timeline; // Mask the timeline. playbackInfo = playbackInfo.copyWithTimeline(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 49669f757f..5a5e585b20 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 @@ -921,31 +921,100 @@ public final class ExoPlayerTest { } @Test - public void illegalSeekPositionDoesThrow() throws Exception { - final IllegalSeekPositionException[] exception = new IllegalSeekPositionException[1]; - ActionSchedule actionSchedule = - new ActionSchedule.Builder(TAG) - .waitForPlaybackState(Player.STATE_BUFFERING) - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(ExoPlayer player) { - try { - player.seekTo(/* mediaItemIndex= */ 100, /* positionMs= */ 0); - } catch (IllegalSeekPositionException e) { - exception[0] = e; - } - } - }) - .waitForPlaybackState(Player.STATE_ENDED) - .build(); - new ExoPlayerTestRunner.Builder(context) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilActionScheduleFinished(TIMEOUT_MS) - .blockUntilEnded(TIMEOUT_MS); - assertThat(exception[0]).isNotNull(); + public void seekTo_indexLargerThanPlaylist_isIgnored() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setMediaItem(MediaItem.fromUri("http://test")); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1000); + + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + player.release(); + } + + @Test + public void addMediaItems_indexLargerThanPlaylist_addsToEndOfPlaylist() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setMediaItem(MediaItem.fromUri("http://test")); + ImmutableList addedItems = + ImmutableList.of(MediaItem.fromUri("http://new1"), MediaItem.fromUri("http://new2")); + + player.addMediaItems(/* index= */ 5000, addedItems); + + assertThat(player.getMediaItemCount()).isEqualTo(3); + assertThat(player.getMediaItemAt(1)).isEqualTo(addedItems.get(0)); + assertThat(player.getMediaItemAt(2)).isEqualTo(addedItems.get(1)); + player.release(); + } + + @Test + public void removeMediaItems_fromIndexLargerThanPlaylist_isIgnored() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setMediaItems( + ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2"))); + + player.removeMediaItems(/* fromIndex= */ 5000, /* toIndex= */ 6000); + + assertThat(player.getMediaItemCount()).isEqualTo(2); + player.release(); + } + + @Test + public void removeMediaItems_toIndexLargerThanPlaylist_removesUpToEndOfPlaylist() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setMediaItems( + ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2"))); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 6000); + + assertThat(player.getMediaItemCount()).isEqualTo(1); + assertThat(player.getMediaItemAt(0).localConfiguration.uri.toString()) + .isEqualTo("http://item1"); + player.release(); + } + + @Test + public void moveMediaItems_fromIndexLargerThanPlaylist_isIgnored() throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + ImmutableList items = + ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2")); + player.setMediaItems(items); + + player.moveMediaItems(/* fromIndex= */ 5000, /* toIndex= */ 6000, /* newIndex= */ 0); + + assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(0)); + assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(1)); + player.release(); + } + + @Test + public void moveMediaItems_toIndexLargerThanPlaylist_movesItemsUpToEndOfPlaylist() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + ImmutableList items = + ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2")); + player.setMediaItems(items); + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 6000, /* newIndex= */ 0); + + assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(1)); + assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(0)); + player.release(); + } + + @Test + public void moveMediaItems_newIndexLargerThanPlaylist_movesItemsUpToEndOfPlaylist() + throws Exception { + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + ImmutableList items = + ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2")); + player.setMediaItems(items); + + player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1, /* newIndex= */ 5000); + + assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(1)); + assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(0)); + player.release(); } @Test From 0ab7c752d7be3f155cdf401acadafa3e4fcccbcc Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 16 Dec 2022 16:39:12 +0000 Subject: [PATCH 057/104] Check if codec still exists before handling tunneling events The tunneling callbacks are sent via Handler messages and may be handled after the codec/surface was changed or released. We already guard against the codec/surface change condition by creating a new listener and verifying that the current callback happens for the correct listener instance, but we don't guard against a released codec yet. PiperOrigin-RevId: 495882353 (cherry picked from commit 5e23b8bfd5a9a9542c2ab8d23ae51c1689d8ff51) --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 45d0bd1726..9716a4e854 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -2047,7 +2047,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void handleFrameRendered(long presentationTimeUs) { - if (this != tunnelingOnFrameRenderedListener) { + if (this != tunnelingOnFrameRenderedListener || getCodec() == null) { // Stale event. return; } From 2c17c6ef04178d10f0b13f663926a69c04b40f7d Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 19 Dec 2022 12:16:35 +0000 Subject: [PATCH 058/104] Add playlist and seek operations to SimpleBasePlayer These are the remaining setter operations. They all share the same logic that handles playlist and/or position changes. The logic to create the placeholder state is mostly copied from ExoPlayerImpl's maskTimelineAndPosition and getPeriodPositonUsAfterTimelineChanged. PiperOrigin-RevId: 496364712 (cherry picked from commit 09d37641d1ef3b8cf26dc39cfcd317ebe3ef5c79) --- .../android/exoplayer2/SimpleBasePlayer.java | 435 +- .../exoplayer2/SimpleBasePlayerTest.java | 3546 ++++++++++++++++- 2 files changed, 3954 insertions(+), 27 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index d501170103..27d02c9d31 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -22,6 +22,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.usToMs; import static java.lang.Math.max; +import static java.lang.Math.min; import android.graphics.Rect; import android.os.Looper; @@ -52,6 +53,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -2021,33 +2023,118 @@ public abstract class SimpleBasePlayer extends BasePlayer { @Override public final void setMediaItems(List mediaItems, boolean resetPosition) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + int startIndex = resetPosition ? C.INDEX_UNSET : state.currentMediaItemIndex; + long startPositionMs = resetPosition ? C.TIME_UNSET : state.contentPositionMsSupplier.get(); + setMediaItemsInternal(mediaItems, startIndex, startPositionMs); } @Override public final void setMediaItems( List mediaItems, int startIndex, long startPositionMs) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + if (startIndex == C.INDEX_UNSET) { + startIndex = state.currentMediaItemIndex; + startPositionMs = state.contentPositionMsSupplier.get(); + } + setMediaItemsInternal(mediaItems, startIndex, startPositionMs); + } + + @RequiresNonNull("state") + private void setMediaItemsInternal( + List mediaItems, int startIndex, long startPositionMs) { + checkArgument(startIndex == C.INDEX_UNSET || startIndex >= 0); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) + && (mediaItems.size() != 1 || !shouldHandleCommand(Player.COMMAND_SET_MEDIA_ITEM))) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSetMediaItems(mediaItems, startIndex, startPositionMs), + /* placeholderStateSupplier= */ () -> { + ArrayList placeholderPlaylist = new ArrayList<>(); + for (int i = 0; i < mediaItems.size(); i++) { + placeholderPlaylist.add(getPlaceholderMediaItemData(mediaItems.get(i))); + } + return getStateWithNewPlaylistAndPosition( + state, placeholderPlaylist, startIndex, startPositionMs); + }); } @Override public final void addMediaItems(int index, List mediaItems) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + checkArgument(index >= 0); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + int playlistSize = state.playlist.size(); + if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || mediaItems.isEmpty()) { + return; + } + int correctedIndex = min(index, playlistSize); + updateStateForPendingOperation( + /* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems), + /* placeholderStateSupplier= */ () -> { + ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); + for (int i = 0; i < mediaItems.size(); i++) { + placeholderPlaylist.add( + i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i))); + } + return getStateWithNewPlaylist(state, placeholderPlaylist, period); + }); } @Override public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + checkArgument(fromIndex >= 0 && toIndex >= fromIndex && newIndex >= 0); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + int playlistSize = state.playlist.size(); + if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) + || playlistSize == 0 + || fromIndex >= playlistSize) { + return; + } + int correctedToIndex = min(toIndex, playlistSize); + int correctedNewIndex = min(newIndex, state.playlist.size() - (correctedToIndex - fromIndex)); + if (fromIndex == correctedToIndex || correctedNewIndex == fromIndex) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleMoveMediaItems( + fromIndex, correctedToIndex, correctedNewIndex), + /* placeholderStateSupplier= */ () -> { + ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); + Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex); + return getStateWithNewPlaylist(state, placeholderPlaylist, period); + }); } @Override public final void removeMediaItems(int fromIndex, int toIndex) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + checkArgument(fromIndex >= 0 && toIndex >= fromIndex); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + int playlistSize = state.playlist.size(); + if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) + || playlistSize == 0 + || fromIndex >= playlistSize) { + return; + } + int correctedToIndex = min(toIndex, playlistSize); + if (fromIndex == correctedToIndex) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex), + /* placeholderStateSupplier= */ () -> { + ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); + Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex); + return getStateWithNewPlaylist(state, placeholderPlaylist, period); + }); } @Override @@ -2141,8 +2228,21 @@ public abstract class SimpleBasePlayer extends BasePlayer { long positionMs, @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { - // TODO: implement. - throw new IllegalStateException(); + verifyApplicationThreadAndInitState(); + checkArgument(mediaItemIndex >= 0); + // Use a local copy to ensure the lambda below uses the current state value. + State state = this.state; + if (!shouldHandleCommand(seekCommand) + || isPlayingAd() + || (!state.playlist.isEmpty() && mediaItemIndex >= state.playlist.size())) { + return; + } + updateStateForPendingOperation( + /* pendingOperation= */ handleSeek(mediaItemIndex, positionMs, seekCommand), + /* placeholderStateSupplier= */ () -> + getStateWithNewPlaylistAndPosition(state, state.playlist, mediaItemIndex, positionMs), + /* seeked= */ true, + isRepeatingCurrentItem); } @Override @@ -2617,7 +2717,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { if (!pendingOperations.isEmpty() || released) { return; } - updateStateAndInformListeners(getState()); + updateStateAndInformListeners( + getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false); } /** @@ -2653,6 +2754,26 @@ public abstract class SimpleBasePlayer extends BasePlayer { return suggestedPlaceholderState; } + /** + * Returns the placeholder {@link MediaItemData} used for a new {@link MediaItem} added to the + * playlist. + * + *

      An implementation only needs to override this method if it can determine a more accurate + * placeholder state than the default. + * + * @param mediaItem The {@link MediaItem} added to the playlist. + * @return The {@link MediaItemData} used as placeholder while adding the item to the playlist is + * in progress. + */ + @ForOverride + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return new MediaItemData.Builder(new PlaceholderUid()) + .setMediaItem(mediaItem) + .setIsDynamic(true) + .setIsPlaceholder(true) + .build(); + } + /** * Handles calls to {@link Player#setPlayWhenReady}, {@link Player#play} and {@link Player#pause}. * @@ -2877,6 +2998,101 @@ public abstract class SimpleBasePlayer extends BasePlayer { throw new IllegalStateException(); } + /** + * Handles calls to {@link Player#setMediaItem} and {@link Player#setMediaItems}. + * + *

      Will only be called if {@link Player#COMMAND_SET_MEDIA_ITEM} or {@link + * Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. If only {@link Player#COMMAND_SET_MEDIA_ITEM} + * is available, the list of media items will always contain exactly one item. + * + * @param mediaItems The media items to add. + * @param startIndex The index at which to start playback from, or {@link C#INDEX_UNSET} to start + * at the default item. + * @param startPositionMs The position in milliseconds to start playback from, or {@link + * C#TIME_UNSET} to start at the default position in the media item. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#addMediaItem} and {@link Player#addMediaItems}. + * + *

      Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. + * + * @param index The index at which to add the items. The index is in the range 0 <= {@code + * index} <= {@link #getMediaItemCount()}. + * @param mediaItems The media items to add. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#moveMediaItem} and {@link Player#moveMediaItems}. + * + *

      Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. + * + * @param fromIndex The start index of the items to move. The index is in the range 0 <= {@code + * fromIndex} < {@link #getMediaItemCount()}. + * @param toIndex The index of the first item not to be included in the move (exclusive). The + * index is in the range {@code fromIndex} < {@code toIndex} <= {@link + * #getMediaItemCount()}. + * @param newIndex The new index of the first moved item. The index is in the range {@code 0} + * <= {@code newIndex} < {@link #getMediaItemCount() - (toIndex - fromIndex)}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}. + * + *

      Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. + * + * @param fromIndex The index at which to start removing media items. The index is in the range 0 + * <= {@code fromIndex} < {@link #getMediaItemCount()}. + * @param toIndex The index of the first item to be kept (exclusive). The index is in the range + * {@code fromIndex} < {@code toIndex} <= {@link #getMediaItemCount()}. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + throw new IllegalStateException(); + } + + /** + * Handles calls to {@link Player#seekTo} and other seek operations (for example, {@link + * Player#seekToNext}). + * + *

      Will only be called if the appropriate {@link Player.Command}, for example {@link + * Player#COMMAND_SEEK_TO_MEDIA_ITEM} or {@link Player#COMMAND_SEEK_TO_NEXT}, is available. + * + * @param mediaItemIndex The media item index to seek to. The index is in the range 0 <= {@code + * mediaItemIndex} < {@code mediaItems.size()}. + * @param positionMs The position in milliseconds to start playback from, or {@link C#TIME_UNSET} + * to start at the default position in the media item. + * @param seekCommand The {@link Player.Command} used to trigger the seek. + * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} + * changes caused by this call. + */ + @ForOverride + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + throw new IllegalStateException(); + } + @RequiresNonNull("state") private boolean shouldHandleCommand(@Player.Command int commandCode) { return !released && state.availableCommands.contains(commandCode); @@ -2884,7 +3100,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { @SuppressWarnings("deprecation") // Calling deprecated listener methods. @RequiresNonNull("state") - private void updateStateAndInformListeners(State newState) { + private void updateStateAndInformListeners( + State newState, boolean seeked, boolean isRepeatingCurrentItem) { State previousState = state; // Assign new state immediately such that all getters return the right values, but use a // snapshot of the previous and new state so that listener invocations are triggered correctly. @@ -2906,10 +3123,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState); MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState); int positionDiscontinuityReason = - getPositionDiscontinuityReason(previousState, newState, window, period); + getPositionDiscontinuityReason(previousState, newState, seeked, window, period); boolean timelineChanged = !previousState.timeline.equals(newState.timeline); int mediaItemTransitionReason = - getMediaItemTransitionReason(previousState, newState, positionDiscontinuityReason, window); + getMediaItemTransitionReason( + previousState, newState, positionDiscontinuityReason, isRepeatingCurrentItem, window); if (timelineChanged) { @Player.TimelineChangeReason @@ -3093,7 +3311,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { listeners.queueEvent( Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata)); } - if (false /* TODO: add flag to know when a seek request has been resolved */) { + if (positionDiscontinuityReason == Player.DISCONTINUITY_REASON_SEEK) { listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed); } if (!previousState.availableCommands.equals(newState.availableCommands)) { @@ -3125,18 +3343,33 @@ public abstract class SimpleBasePlayer extends BasePlayer { @RequiresNonNull("state") private void updateStateForPendingOperation( ListenableFuture pendingOperation, Supplier placeholderStateSupplier) { + updateStateForPendingOperation( + pendingOperation, + placeholderStateSupplier, + /* seeked= */ false, + /* isRepeatingCurrentItem= */ false); + } + + @RequiresNonNull("state") + private void updateStateForPendingOperation( + ListenableFuture pendingOperation, + Supplier placeholderStateSupplier, + boolean seeked, + boolean isRepeatingCurrentItem) { if (pendingOperation.isDone() && pendingOperations.isEmpty()) { - updateStateAndInformListeners(getState()); + updateStateAndInformListeners(getState(), seeked, isRepeatingCurrentItem); } else { pendingOperations.add(pendingOperation); State suggestedPlaceholderState = placeholderStateSupplier.get(); - updateStateAndInformListeners(getPlaceholderState(suggestedPlaceholderState)); + updateStateAndInformListeners( + getPlaceholderState(suggestedPlaceholderState), seeked, isRepeatingCurrentItem); pendingOperation.addListener( () -> { castNonNull(state); // Already checked by method @RequiresNonNull pre-condition. pendingOperations.remove(pendingOperation); if (pendingOperations.isEmpty() && !released) { - updateStateAndInformListeners(getState()); + updateStateAndInformListeners( + getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false); } }, this::postOrRunOnApplicationHandler); @@ -3221,7 +3454,11 @@ public abstract class SimpleBasePlayer extends BasePlayer { return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; } for (int i = 0; i < previousPlaylist.size(); i++) { - if (!previousPlaylist.get(i).uid.equals(newPlaylist.get(i).uid)) { + Object previousUid = previousPlaylist.get(i).uid; + Object newUid = newPlaylist.get(i).uid; + boolean resolvedAutoGeneratedPlaceholder = + previousUid instanceof PlaceholderUid && !(newUid instanceof PlaceholderUid); + if (!previousUid.equals(newUid) && !resolvedAutoGeneratedPlaceholder) { return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; } } @@ -3229,11 +3466,18 @@ public abstract class SimpleBasePlayer extends BasePlayer { } private static int getPositionDiscontinuityReason( - State previousState, State newState, Timeline.Window window, Timeline.Period period) { + State previousState, + State newState, + boolean seeked, + Timeline.Window window, + Timeline.Period period) { if (newState.hasPositionDiscontinuity) { // We were asked to report a discontinuity. return newState.positionDiscontinuityReason; } + if (seeked) { + return Player.DISCONTINUITY_REASON_SEEK; + } if (previousState.playlist.isEmpty()) { // First change from an empty playlist is not reported as a discontinuity. return C.INDEX_UNSET; @@ -3247,6 +3491,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { getCurrentPeriodIndexInternal(previousState, window, period)); Object newPeriodUid = newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window, period)); + if (previousPeriodUid instanceof PlaceholderUid && !(newPeriodUid instanceof PlaceholderUid)) { + // An auto-generated placeholder was resolved to a real item. + return C.INDEX_UNSET; + } if (!newPeriodUid.equals(previousPeriodUid) || previousState.currentAdGroupIndex != newState.currentAdGroupIndex || previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) { @@ -3343,6 +3591,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { State previousState, State newState, int positionDiscontinuityReason, + boolean isRepeatingCurrentItem, Timeline.Window window) { Timeline previousTimeline = previousState.timeline; Timeline newTimeline = newState.timeline; @@ -3356,6 +3605,10 @@ public abstract class SimpleBasePlayer extends BasePlayer { .uid; Object newWindowUid = newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window).uid; + if (previousWindowUid instanceof PlaceholderUid && !(newWindowUid instanceof PlaceholderUid)) { + // An auto-generated placeholder was resolved to a real item. + return C.INDEX_UNSET; + } if (!previousWindowUid.equals(newWindowUid)) { if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { return MEDIA_ITEM_TRANSITION_REASON_AUTO; @@ -3371,8 +3624,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { && getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) { return MEDIA_ITEM_TRANSITION_REASON_REPEAT; } - if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK - && /* TODO: mark repetition seeks to detect this case */ false) { + if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && isRepeatingCurrentItem) { return MEDIA_ITEM_TRANSITION_REASON_SEEK; } return C.INDEX_UNSET; @@ -3385,4 +3637,139 @@ public abstract class SimpleBasePlayer extends BasePlayer { Rect surfaceFrame = surfaceHolder.getSurfaceFrame(); return new Size(surfaceFrame.width(), surfaceFrame.height()); } + + private static int getMediaItemIndexInNewPlaylist( + List oldPlaylist, + Timeline newPlaylistTimeline, + int oldMediaItemIndex, + Timeline.Period period) { + if (oldPlaylist.isEmpty()) { + return oldMediaItemIndex < newPlaylistTimeline.getWindowCount() + ? oldMediaItemIndex + : C.INDEX_UNSET; + } + Object oldFirstPeriodUid = + oldPlaylist.get(oldMediaItemIndex).getPeriodUid(/* periodIndexInMediaItem= */ 0); + if (newPlaylistTimeline.getIndexOfPeriod(oldFirstPeriodUid) == C.INDEX_UNSET) { + return C.INDEX_UNSET; + } + return newPlaylistTimeline.getPeriodByUid(oldFirstPeriodUid, period).windowIndex; + } + + private static State getStateWithNewPlaylist( + State oldState, List newPlaylist, Timeline.Period period) { + State.Builder stateBuilder = oldState.buildUpon(); + stateBuilder.setPlaylist(newPlaylist); + Timeline newTimeline = stateBuilder.timeline; + long oldPositionMs = oldState.contentPositionMsSupplier.get(); + int oldIndex = getCurrentMediaItemIndexInternal(oldState); + int newIndex = getMediaItemIndexInNewPlaylist(oldState.playlist, newTimeline, oldIndex, period); + long newPositionMs = newIndex == C.INDEX_UNSET ? C.TIME_UNSET : oldPositionMs; + // If the current item no longer exists, try to find a matching subsequent item. + for (int i = oldIndex + 1; newIndex == C.INDEX_UNSET && i < oldState.playlist.size(); i++) { + // TODO: Use shuffle order to iterate. + newIndex = + getMediaItemIndexInNewPlaylist( + oldState.playlist, newTimeline, /* oldMediaItemIndex= */ i, period); + } + // If this fails, transition to ENDED state. + if (oldState.playbackState != Player.STATE_IDLE && newIndex == C.INDEX_UNSET) { + stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false); + } + return buildStateForNewPosition( + stateBuilder, + oldState, + oldPositionMs, + newPlaylist, + newIndex, + newPositionMs, + /* keepAds= */ true); + } + + private static State getStateWithNewPlaylistAndPosition( + State oldState, List newPlaylist, int newIndex, long newPositionMs) { + State.Builder stateBuilder = oldState.buildUpon(); + stateBuilder.setPlaylist(newPlaylist); + if (oldState.playbackState != Player.STATE_IDLE) { + if (newPlaylist.isEmpty()) { + stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false); + } else { + stateBuilder.setPlaybackState(Player.STATE_BUFFERING); + } + } + long oldPositionMs = oldState.contentPositionMsSupplier.get(); + return buildStateForNewPosition( + stateBuilder, + oldState, + oldPositionMs, + newPlaylist, + newIndex, + newPositionMs, + /* keepAds= */ false); + } + + private static State buildStateForNewPosition( + State.Builder stateBuilder, + State oldState, + long oldPositionMs, + List newPlaylist, + int newIndex, + long newPositionMs, + boolean keepAds) { + // Resolve unset or invalid index and position. + oldPositionMs = getPositionOrDefaultInMediaItem(oldPositionMs, oldState); + if (!newPlaylist.isEmpty() && (newIndex == C.INDEX_UNSET || newIndex >= newPlaylist.size())) { + newIndex = 0; // TODO: Use shuffle order to get first index. + newPositionMs = C.TIME_UNSET; + } + if (!newPlaylist.isEmpty() && newPositionMs == C.TIME_UNSET) { + newPositionMs = usToMs(newPlaylist.get(newIndex).defaultPositionUs); + } + boolean oldOrNewPlaylistEmpty = oldState.playlist.isEmpty() || newPlaylist.isEmpty(); + boolean mediaItemChanged = + !oldOrNewPlaylistEmpty + && !oldState + .playlist + .get(getCurrentMediaItemIndexInternal(oldState)) + .uid + .equals(newPlaylist.get(newIndex).uid); + if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) { + // New item or seeking back. Assume no buffer and no ad playback persists. + stateBuilder + .setCurrentMediaItemIndex(newIndex) + .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) + .setContentPositionMs(newPositionMs) + .setContentBufferedPositionMs(PositionSupplier.getConstant(newPositionMs)) + .setTotalBufferedDurationMs(PositionSupplier.ZERO); + } else if (newPositionMs == oldPositionMs) { + // Unchanged position. Assume ad playback and buffer in current item persists. + stateBuilder.setCurrentMediaItemIndex(newIndex); + if (oldState.currentAdGroupIndex != C.INDEX_UNSET && keepAds) { + stateBuilder.setTotalBufferedDurationMs( + PositionSupplier.getConstant( + oldState.adBufferedPositionMsSupplier.get() - oldState.adPositionMsSupplier.get())); + } else { + stateBuilder + .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) + .setTotalBufferedDurationMs( + PositionSupplier.getConstant( + getContentBufferedPositionMsInternal(oldState) - oldPositionMs)); + } + } else { + // Seeking forward. Assume remaining buffer in current item persist, but no ad playback. + long contentBufferedDurationMs = + max(getContentBufferedPositionMsInternal(oldState), newPositionMs); + long totalBufferedDurationMs = + max(0, oldState.totalBufferedDurationMsSupplier.get() - (newPositionMs - oldPositionMs)); + stateBuilder + .setCurrentMediaItemIndex(newIndex) + .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) + .setContentPositionMs(newPositionMs) + .setContentBufferedPositionMs(PositionSupplier.getConstant(contentBufferedDurationMs)) + .setTotalBufferedDurationMs(PositionSupplier.getConstant(totalBufferedDurationMs)); + } + return stateBuilder.build(); + } + + private static final class PlaceholderUid {} } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java index 3cb6f57de4..4019f9668d 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/SimpleBasePlayerTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -56,7 +57,9 @@ import com.google.common.util.concurrent.SettableFuture; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.Ignore; import org.junit.Test; @@ -1400,6 +1403,7 @@ public class SimpleBasePlayerTest { /* adIndexInAdGroup= */ C.INDEX_UNSET), Player.DISCONTINUITY_REASON_SEEK); verify(listener).onMediaItemTransition(mediaItem1, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener).onSeekProcessed(); verify(listener) .onEvents( player, @@ -1439,9 +1443,6 @@ public class SimpleBasePlayerTest { verifyNoMoreInteractions(listener); // Assert that we actually called all listeners. for (Method method : Player.Listener.class.getDeclaredMethods()) { - if (method.getName().equals("onSeekProcessed")) { - continue; - } if (method.getName().equals("onAudioSessionIdChanged") || method.getName().equals("onSkipSilenceEnabledChanged")) { // Skip listeners for ExoPlayer-specific states @@ -3817,6 +3818,3545 @@ public class SimpleBasePlayerTest { assertThat(callForwarded.get()).isFalse(); } + @Test + public void addMediaItems_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.addMediaItems( + /* index= */ 1, + ImmutableList.of( + new MediaItem.Builder().setMediaId("3").build(), + new MediaItem.Builder().setMediaId("4").build())); + + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void addMediaItems_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(3) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.addMediaItems( + /* index= */ 1, + ImmutableList.of( + new MediaItem.Builder().setMediaId("3").build(), + new MediaItem.Builder().setMediaId("4").build())); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(3); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(4); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + assertThat(window.isPlaceholder).isFalse(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window); + assertThat(window.mediaItem.mediaId).isEqualTo("4"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 3, window); + assertThat(window.uid).isEqualTo(2); + assertThat(window.isPlaceholder).isFalse(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(3); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + addMediaItems_asyncHandlingWhileAdIsPlaying_usesPlaceholderStateAndInformsListeners() { + SimpleBasePlayer.PeriodData adPeriodData = + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 123)) + .build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setPeriods(ImmutableList.of(adPeriodData)) + .build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setPeriods(ImmutableList.of(adPeriodData)) + .build())) + .setCurrentMediaItemIndex(1) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.addMediaItem(/* index= */ 0, new MediaItem.Builder().setMediaId("id").build()); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("id"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(1); + assertThat(window.isPlaceholder).isFalse(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void addMediaItems_asyncHandlingFromEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.addMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build())); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + addMediaItems_asyncHandlingFromEmptyWithPreviouslySetPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.addMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem)); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + addMediaItems_asyncHandlingFromEmptyWithPreviouslySetPositionExceedingNewPlaylistSize_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(5000) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(1000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(1_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.addMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build())); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(1000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(1000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + addMediaItems_asyncHandlingFromEmptyWithPreviouslySetIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.addMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem)); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void addMediaItems_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_CHANGE_MEDIA_ITEMS) + .build()) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.addMediaItem(new MediaItem.Builder().setMediaId("id").build()); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void addMediaItems_withInvalidIndex_addsToEndOfPlaylist() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicInteger indexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { + indexInHandleMethod.set(index); + return SettableFuture.create(); + } + }; + + player.addMediaItem(/* index= */ 5000, new MediaItem.Builder().setMediaId("new").build()); + + assertThat(indexInHandleMethod.get()).isEqualTo(1); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("new"); + } + + @Test + public void moveMediaItems_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .setCurrentMediaItemIndex(2) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleMoveMediaItems( + int fromIndex, int toIndex, int newIndex) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); + + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void moveMediaItems_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .setCurrentMediaItemIndex(2) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleMoveMediaItems( + int fromIndex, int toIndex, int newIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(2); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(3); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(2); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(3); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window); + assertThat(window.uid).isEqualTo(1); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(2); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void + moveMediaItems_asyncHandlingWhileAdIsPlaying_usesPlaceholderStateAndInformsListeners() { + SimpleBasePlayer.PeriodData adPeriodData = + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 123)) + .build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setPeriods(ImmutableList.of(adPeriodData)) + .build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setPeriods(ImmutableList.of(adPeriodData)) + .build())) + .setCurrentMediaItemIndex(2) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleMoveMediaItems( + int fromIndex, int toIndex, int newIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 0); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(2); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(3); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(2); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(3); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window); + assertThat(window.uid).isEqualTo(1); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(2); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void moveMediaItems_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_CHANGE_MEDIA_ITEMS) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleMoveMediaItems( + int fromIndex, int toIndex, int newIndex) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, /* newIndex= */ 0); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void moveMediaItems_withInvalidIndices_usesValidIndexRange() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .build(); + AtomicInteger fromIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger toIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger newIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleMoveMediaItems( + int fromIndex, int toIndex, int newIndex) { + fromIndexInHandleMethod.set(fromIndex); + toIndexInHandleMethod.set(toIndex); + newIndexInHandleMethod.set(newIndex); + return SettableFuture.create(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2500, /* newIndex= */ 0); + assertThat(fromIndexInHandleMethod.get()).isEqualTo(1); + assertThat(toIndexInHandleMethod.get()).isEqualTo(3); + assertThat(newIndexInHandleMethod.get()).isEqualTo(0); + + player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 6000); + assertThat(fromIndexInHandleMethod.get()).isEqualTo(0); + assertThat(toIndexInHandleMethod.get()).isEqualTo(2); + assertThat(newIndexInHandleMethod.get()).isEqualTo(1); + + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(3); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(2); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window); + assertThat(window.uid).isEqualTo(3); + verify(listener, times(2)) + .onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); + verifyNoMoreInteractions(listener); + } + + @Test + public void removeMediaItems_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build())) + .setCurrentMediaItemIndex(3) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build())) + .setCurrentMediaItemIndex(1) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3); + + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void removeMediaItems_asyncHandling_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build())) + .setCurrentMediaItemIndex(3) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 5).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build())) + .setCurrentMediaItemIndex(1) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(4); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @Test + public void + removeMediaItems_asyncHandlingWhileAdIsPlaying_usesPlaceholderStateAndInformsListeners() { + SimpleBasePlayer.PeriodData adPeriodData = + new SimpleBasePlayer.PeriodData.Builder(/* uid= */ new Object()) + .setAdPlaybackState( + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 123)) + .build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4) + .setPeriods(ImmutableList.of(adPeriodData)) + .build())) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) + .setCurrentMediaItemIndex(3) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 5).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4) + .setPeriods(ImmutableList.of(adPeriodData)) + .build())) + .setCurrentMediaItemIndex(1) + .setCurrentAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(4); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentAdGroupIndex()).isEqualTo(0); + assertThat(player.getCurrentAdIndexInAdGroup()).isEqualTo(0); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Testing deprecated listener call. + @Test + public void + removeMediaItems_asyncHandlingRemovingCurrentItemWithSubsequentMatch_usesPlaceholderStateAndInformsListeners() { + MediaItem lastMediaItem = new MediaItem.Builder().setMediaId("id").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4) + .setMediaItem(lastMediaItem) + .build())) + .setCurrentMediaItemIndex(1) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 5).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4) + .setMediaItem(lastMediaItem) + .build())) + .setCurrentMediaItemIndex(1) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.uid).isEqualTo(4); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition(lastMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Testing deprecated listener call. + @Test + public void + removeMediaItems_asyncHandlingRemovingCurrentItemWithoutSubsequentMatch_usesPlaceholderStateAndInformsListeners() { + MediaItem firstMediaItem = new MediaItem.Builder().setMediaId("id").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setMediaItem(firstMediaItem) + .build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .setCurrentMediaItemIndex(1) + .setPlaybackState(Player.STATE_READY) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setMediaItem(firstMediaItem) + .build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 5).build())) + .setCurrentMediaItemIndex(0) + .setPlaybackState(Player.STATE_ENDED) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(1); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition( + firstMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onPlaybackStateChanged(Player.STATE_ENDED); + verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_ENDED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Testing deprecated listener call. + @Test + public void + removeMediaItems_asyncHandlingRemovingEntirePlaylist_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build())) + .setCurrentMediaItemIndex(1) + .setPlaybackState(Player.STATE_READY) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state + .buildUpon() + .setPlaylist(ImmutableList.of()) + .setCurrentMediaItemIndex(C.INDEX_UNSET) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .setPlaybackState(Player.STATE_ENDED) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.clearMediaItems(); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onPlaybackStateChanged(Player.STATE_ENDED); + verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_ENDED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener).onRepeatModeChanged(Player.REPEAT_MODE_ALL); + verifyNoMoreInteractions(listener); + } + + @Test + public void removeMediaItems_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_CHANGE_MEDIA_ITEMS) + .build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.removeMediaItem(/* index= */ 0); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void removeMediaItems_withInvalidIndex_removesToEndOfPlaylist() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicInteger fromIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + AtomicInteger toIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { + fromIndexInHandleMethod.set(fromIndex); + toIndexInHandleMethod.set(toIndex); + return SettableFuture.create(); + } + }; + + player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 5000); + + assertThat(fromIndexInHandleMethod.get()).isEqualTo(1); + assertThat(toIndexInHandleMethod.get()).isEqualTo(2); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(1); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.uid).isEqualTo(1); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void setMediaItems_immediateHandling_updatesStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("new").build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3) + .setMediaItem(newMediaItem) + .build())) + .setCurrentMediaItemIndex(1) + .build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems( + ImmutableList.of( + new MediaItem.Builder().setMediaId("2").build(), + new MediaItem.Builder().setMediaId("3").build())); + + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + setMediaItems_asyncHandlingWithIndexAndPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem), + /* startIndex= */ 1, + /* startPositionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithIndexAndPositionFromEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("1").build(), newMediaItem), + /* startIndex= */ 1, + /* startPositionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("1"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(3_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("1").build(), newMediaItem), + /* startIndex= */ 1, + /* startPositionMs= */ C.TIME_UNSET); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("1"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + setMediaItems_asyncHandlingWithEmptyPlaylistAndIndexAndPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist(ImmutableList.of()) + .setCurrentMediaItemIndex(20) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* startIndex= */ 20, /* startPositionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(20); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verify(listener) + .onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(20); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + setMediaItems_asyncHandlingWithEmptyPlaylistAndIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + State updatedState = + state.buildUpon().setPlaylist(ImmutableList.of()).setCurrentMediaItemIndex(20).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems( + ImmutableList.of(), /* startIndex= */ 20, /* startPositionMs= */ C.TIME_UNSET); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(20); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verify(listener) + .onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(20); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void setMediaItems_asyncHandlingWithResetTrue_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.setMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build()), + /* resetPosition= */ true); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetTrueFromEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.setMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build()), + /* resetPosition= */ true); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + setMediaItems_asyncHandlingWithResetTrueToEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist(ImmutableList.of()) + .setCurrentMediaItemIndex(C.INDEX_UNSET) + .setContentPositionMs(C.TIME_UNSET) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ true); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verify(listener) + .onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetTrueFromEmptyToEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setCurrentMediaItemIndex(C.INDEX_UNSET) + .setContentPositionMs(C.TIME_UNSET) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ true); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void setMediaItems_asyncHandlingWithResetFalse_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem), + /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyWithSetPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem), + /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyWithSetPositionExceedingPlaylistSize_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(5000) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(0) + .setContentPositionMs(1000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(1_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.setMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build()), + /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(1000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(1000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyWithIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + + player.setMediaItems( + ImmutableList.of(new MediaItem.Builder().setMediaId("3").build(), newMediaItem), + /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyWithDefaultIndexAndPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setContentPositionMs(5000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + + @Override + protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { + return super.getPlaceholderMediaItemData(mediaItem) + .buildUpon() + .setDefaultPositionUs(5_000_000) + .build(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("3").build(); + + player.setMediaItems( + ImmutableList.of(newMediaItem, new MediaItem.Builder().setMediaId("2").build()), + /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2); + Timeline.Window window = new Timeline.Window(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window); + assertThat(window.mediaItem.mediaId).isEqualTo("3"); + assertThat(window.isPlaceholder).isTrue(); + player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window); + assertThat(window.mediaItem.mediaId).isEqualTo("2"); + assertThat(window.isPlaceholder).isTrue(); + verify(listener) + .onTimelineChanged( + player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5000); + assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline); + verify(listener) + .onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + setMediaItems_asyncHandlingWithResetFalseToEmpty_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + State updatedState = + state + .buildUpon() + .setPlaylist(ImmutableList.of()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verify(listener) + .onTimelineChanged(Timeline.EMPTY, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); + verify(listener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE)); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyToEmptyWithSetPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .setContentPositionMs(3000) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyToEmptyWithIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setCurrentMediaItemIndex(1) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @Test + public void + setMediaItems_asyncHandlingWithResetFalseFromEmptyToEmptyWithDefaultIndexAndPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.setMediaItems(ImmutableList.of(), /* resetPosition= */ false); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getCurrentTimeline().isEmpty()).isTrue(); + verifyNoMoreInteractions(listener); + } + + @Test + public void setMediaItems_withoutAvailableCommandForEmptyPlaylist_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_CHANGE_MEDIA_ITEMS) + .build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setMediaItems(ImmutableList.of()); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setMediaItems_withoutAvailableCommandForSingleItemPlaylist_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .removeAll(Player.COMMAND_CHANGE_MEDIA_ITEMS, Player.COMMAND_SET_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setMediaItems(ImmutableList.of(new MediaItem.Builder().setMediaId("new").build())); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void setMediaItems_withJustSetMediaItemCommandForSingleItemPlaylist_isForwarded() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().add(Player.COMMAND_SET_MEDIA_ITEM).build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setMediaItems(ImmutableList.of(new MediaItem.Builder().setMediaId("new").build())); + + assertThat(callForwarded.get()).isTrue(); + } + + @Test + public void setMediaItems_withJustChangeMediaItemsCommandForSingleItemPlaylist_isForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().add(Player.COMMAND_CHANGE_MEDIA_ITEMS).build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setMediaItems(ImmutableList.of(new MediaItem.Builder().setMediaId("new").build())); + + assertThat(callForwarded.get()).isTrue(); + } + + @Test + public void setMediaItems_withoutAvailableCommandForMultiItemPlaylist_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_CHANGE_MEDIA_ITEMS) + .build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSetMediaItems( + List mediaItems, int startIndex, long startPositionMs) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.setMediaItems( + ImmutableList.of( + new MediaItem.Builder().setMediaId("1").build(), + new MediaItem.Builder().setMediaId("2").build())); + + assertThat(callForwarded.get()).isFalse(); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void seekTo_immediateHandling_updatesStateAndInformsListeners() { + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2) + .setMediaItem(newMediaItem) + .build())) + .build(); + State updatedState = + state.buildUpon().setCurrentMediaItemIndex(1).setContentPositionMs(3000).build(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + private State playerState = state; + + @Override + protected State getState() { + return playerState; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + playerState = updatedState; + return Futures.immediateVoidFuture(); + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000); + + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void seekTo_asyncHandlingWithIndexAndPosition_usesPlaceholderStateAndInformsListeners() { + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2) + .setMediaItem(newMediaItem) + .build())) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state.buildUpon().setCurrentMediaItemIndex(1).setContentPositionMs(3005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getBufferedPosition()).isEqualTo(3000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithIndexAndDefaultPosition_usesPlaceholderStateAndInformsListeners() { + MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2) + .setMediaItem(newMediaItem) + .setDefaultPositionUs(3_000_000) + .build())) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state.buildUpon().setCurrentMediaItemIndex(1).setContentPositionMs(3005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getBufferedPosition()).isEqualTo(3000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithIndexAndPositionAndEmptyPlaylist_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist(ImmutableList.of()) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state.buildUpon().setCurrentMediaItemIndex(1).setContentPositionMs(3005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getBufferedPosition()).isEqualTo(3000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(3005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithIndexAndDefaultPositionAndEmptyPlaylist_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist(ImmutableList.of()) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = + state.buildUpon().setCurrentMediaItemIndex(1).setContentPositionMs(100).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ C.TIME_UNSET); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getBufferedPosition()).isEqualTo(0); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1); + assertThat(player.getCurrentPosition()).isEqualTo(100); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithSeekBackInCurrentItem_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = state.buildUpon().setContentPositionMs(3005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* positionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getBufferedPosition()).isEqualTo(3000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(3005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithSeekToCurrentPosition_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .setContentPositionMs(3000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(7000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = state.buildUpon().setContentPositionMs(3005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* positionMs= */ 3000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(3000); + assertThat(player.getBufferedPosition()).isEqualTo(10000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(7000); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(3005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithSeekForwardInCurrentItem_usesPlaceholderStateAndInformsListeners() { + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build())) + .setContentPositionMs(3000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(7000)) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = state.buildUpon().setContentPositionMs(7005).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekTo(/* positionMs= */ 7000); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(7000); + assertThat(player.getBufferedPosition()).isEqualTo(10000); + assertThat(player.getTotalBufferedDuration()).isEqualTo(3000); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(7005); + verifyNoMoreInteractions(listener); + } + + @SuppressWarnings("deprecation") // Verifying deprecated listener calls. + @Test + public void + seekTo_asyncHandlingWithRepeatOfCurrentItem_usesPlaceholderStateAndInformsListeners() { + MediaItem mediaItem = new MediaItem.Builder().setMediaId("id").build(); + State state = + new State.Builder() + .setAvailableCommands(new Commands.Builder().addAllCommands().build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1) + .setMediaItem(mediaItem) + .build())) + .setContentPositionMs(8000) + .setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier.getConstant(10000)) + .setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier.getConstant(2000)) + .setRepeatMode(Player.REPEAT_MODE_ALL) + .build(); + // Change updated state slightly to see a difference to the placeholder state. + State updatedState = state.buildUpon().setContentPositionMs(5).build(); + SettableFuture future = SettableFuture.create(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return future.isDone() ? updatedState : state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + return future; + } + }; + Listener listener = mock(Listener.class); + player.addListener(listener); + + player.seekToNext(); + + // Verify placeholder state and listener calls. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(0); + assertThat(player.getBufferedPosition()).isEqualTo(0); + assertThat(player.getTotalBufferedDuration()).isEqualTo(0); + verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK); + verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); + verify(listener).onMediaItemTransition(mediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK); + verify(listener).onSeekProcessed(); + verifyNoMoreInteractions(listener); + + future.set(null); + + // Verify actual state update. + assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0); + assertThat(player.getCurrentPosition()).isEqualTo(5); + verifyNoMoreInteractions(listener); + } + + @Test + public void seekTo_withoutAvailableCommandForSeekToMediaItem_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 4000); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekTo_withoutAvailableCommandForSeekInCurrentMediaItem_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekTo(/* positionMs= */ 4000); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekToDefaultPosition_withoutAvailableCommandForSeekToMediaItem_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToDefaultPosition(/* mediaItemIndex= */ 1); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void + seekToDefaultPosition_withoutAvailableCommandForSeekToDefaultPosition_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_DEFAULT_POSITION) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToDefaultPosition(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekBack_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_SEEK_BACK).build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekBack(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekToPrevious_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_PREVIOUS) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToPrevious(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekToPreviousMediaItem_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .setCurrentMediaItemIndex(1) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToPreviousMediaItem(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekForward_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_SEEK_FORWARD).build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekForward(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekToNext_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder().addAllCommands().remove(Player.COMMAND_SEEK_TO_NEXT).build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToNext(); + + assertThat(callForwarded.get()).isFalse(); + } + + @Test + public void seekToNextMediaItem_withoutAvailableCommand_isNotForwarded() { + State state = + new State.Builder() + .setAvailableCommands( + new Commands.Builder() + .addAllCommands() + .remove(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) + .build()) + .setPlaylist( + ImmutableList.of( + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(), + new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build())) + .build(); + AtomicBoolean callForwarded = new AtomicBoolean(); + SimpleBasePlayer player = + new SimpleBasePlayer(Looper.myLooper()) { + @Override + protected State getState() { + return state; + } + + @Override + protected ListenableFuture handleSeek( + int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { + callForwarded.set(true); + return Futures.immediateVoidFuture(); + } + }; + + player.seekToNextMediaItem(); + + assertThat(callForwarded.get()).isFalse(); + } + private static Object[] getAnyArguments(Method method) { Object[] arguments = new Object[method.getParameterCount()]; Class[] argumentTypes = method.getParameterTypes(); From 65daec35965b827c2c90d494bed81f04a8ae257e Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 19 Dec 2022 13:42:29 +0000 Subject: [PATCH 059/104] Remove ellipsis from Player javadoc PiperOrigin-RevId: 496377192 (cherry picked from commit 844428ea329754118b8c0a2d58373415f539d725) --- .../src/main/java/com/google/android/exoplayer2/Player.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 0bc6e543ac..a256b179b2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -2075,7 +2075,7 @@ public interface Player { * setPlaybackParameters(getPlaybackParameters().withSpeed(speed))}. * * @param speed The linear factor by which playback will be sped up. Must be higher than 0. 1 is - * normal speed, 2 is twice as fast, 0.5 is half normal speed... + * normal speed, 2 is twice as fast, 0.5 is half normal speed. */ void setPlaybackSpeed(@FloatRange(from = 0, fromInclusive = false) float speed); From beeb6e7eed32fc0460b4f0efc7efb9d01a9c02c2 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 19 Dec 2022 13:53:30 +0000 Subject: [PATCH 060/104] Fix Dackka error due to param name mismatch https://developer.android.com/reference/androidx/leanback/media/PlayerAdapter#seekTo(long) #minor-release PiperOrigin-RevId: 496378709 (cherry picked from commit d2a3d8f6fabb6d22ac28a76379725d0915344cba) --- .../exoplayer2/ext/leanback/LeanbackPlayerAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java index 95a4de028d..d245aaa07f 100644 --- a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java +++ b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java @@ -162,8 +162,8 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab } @Override - public void seekTo(long positionMs) { - player.seekTo(player.getCurrentMediaItemIndex(), positionMs); + public void seekTo(long positionInMs) { + player.seekTo(player.getCurrentMediaItemIndex(), positionInMs); } @Override From 730f3a3a64c6de2920dbd3f16a90fb4eda69bf21 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 20 Dec 2022 15:56:19 +0000 Subject: [PATCH 061/104] Clarify some Player command and method javadoc #minor-release PiperOrigin-RevId: 496661152 (cherry picked from commit f47ad3c2d097a557bd2222a5b104e7867aef585d) --- .../com/google/android/exoplayer2/Player.java | 162 +++++++++++------- 1 file changed, 99 insertions(+), 63 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index a256b179b2..01525cb3b1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -73,7 +73,7 @@ import java.util.List; */ public interface Player { - /** A set of {@link Event events}. */ + /** A set of {@linkplain Event events}. */ final class Events { private final FlagSet flags; @@ -81,7 +81,7 @@ public interface Player { /** * Creates an instance. * - * @param flags The {@link FlagSet} containing the {@link Event events}. + * @param flags The {@link FlagSet} containing the {@linkplain Event events}. */ public Events(FlagSet flags) { this.flags = flags; @@ -98,10 +98,10 @@ public interface Player { } /** - * Returns whether any of the given {@link Event events} occurred. + * Returns whether any of the given {@linkplain Event events} occurred. * - * @param events The {@link Event events}. - * @return Whether any of the {@link Event events} occurred. + * @param events The {@linkplain Event events}. + * @return Whether any of the {@linkplain Event events} occurred. */ public boolean containsAny(@Event int... events) { return flags.containsAny(events); @@ -211,6 +211,7 @@ public interface Player { } /** Creates an instance. */ + @SuppressWarnings("deprecation") // Setting deprecated windowIndex field public PositionInfo( @Nullable Object windowUid, int mediaItemIndex, @@ -349,7 +350,7 @@ public interface Player { } /** - * A set of {@link Command commands}. + * A set of {@linkplain Command commands}. * *

      Instances are immutable. */ @@ -432,9 +433,9 @@ public interface Player { } /** - * Adds {@link Command commands}. + * Adds {@linkplain Command commands}. * - * @param commands The {@link Command commands} to add. + * @param commands The {@linkplain Command commands} to add. * @return This builder. * @throws IllegalStateException If {@link #build()} has already been called. */ @@ -447,7 +448,7 @@ public interface Player { /** * Adds {@link Commands}. * - * @param commands The set of {@link Command commands} to add. + * @param commands The set of {@linkplain Command commands} to add. * @return This builder. * @throws IllegalStateException If {@link #build()} has already been called. */ @@ -458,7 +459,7 @@ public interface Player { } /** - * Adds all existing {@link Command commands}. + * Adds all existing {@linkplain Command commands}. * * @return This builder. * @throws IllegalStateException If {@link #build()} has already been called. @@ -497,9 +498,9 @@ public interface Player { } /** - * Removes {@link Command commands}. + * Removes {@linkplain Command commands}. * - * @param commands The {@link Command commands} to remove. + * @param commands The {@linkplain Command commands} to remove. * @return This builder. * @throws IllegalStateException If {@link #build()} has already been called. */ @@ -631,7 +632,8 @@ public interface Player { *

      State changes and events that happen within one {@link Looper} message queue iteration are * reported together and only after all individual callbacks were triggered. * - *

      Only state changes represented by {@link Event events} are reported through this method. + *

      Only state changes represented by {@linkplain Event events} are reported through this + * method. * *

      Listeners should prefer this method over individual callbacks in the following cases: * @@ -777,7 +779,7 @@ public interface Player { *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. * - * @param playbackState The new playback {@link State state}. + * @param playbackState The new playback {@link State}. */ default void onPlaybackStateChanged(@State int playbackState) {} @@ -788,7 +790,7 @@ public interface Player { * other events that happen in the same {@link Looper} message queue iteration. * * @param playWhenReady Whether playback will proceed when ready. - * @param reason The {@link PlayWhenReadyChangeReason reason} for the change. + * @param reason The {@link PlayWhenReadyChangeReason} for the change. */ default void onPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {} @@ -830,7 +832,7 @@ public interface Player { *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. * - * @param shuffleModeEnabled Whether shuffling of {@link MediaItem media items} is enabled. + * @param shuffleModeEnabled Whether shuffling of {@linkplain MediaItem media items} is enabled. */ default void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {} @@ -1032,10 +1034,10 @@ public interface Player { default void onRenderedFirstFrame() {} /** - * Called when there is a change in the {@link Cue Cues}. + * Called when there is a change in the {@linkplain Cue cues}. * - *

      Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change - * in the cues. You should only implement one or the other. + *

      Both this method and {@link #onCues(CueGroup)} are called when there is a change in the + * cues. You should only implement one or the other. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -1048,8 +1050,8 @@ public interface Player { /** * Called when there is a change in the {@link CueGroup}. * - *

      Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change - * in the cues. You should only implement one or the other. + *

      Both this method and {@link #onCues(List)} are called when there is a change in the cues. + * You should only implement one or the other. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -1395,21 +1397,47 @@ public interface Player { int EVENT_DEVICE_VOLUME_CHANGED = 30; /** - * Commands that can be executed on a {@code Player}. One of {@link #COMMAND_PLAY_PAUSE}, {@link - * #COMMAND_PREPARE}, {@link #COMMAND_STOP}, {@link #COMMAND_SEEK_TO_DEFAULT_POSITION}, {@link - * #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM}, {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM}, {@link - * #COMMAND_SEEK_TO_PREVIOUS}, {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}, {@link - * #COMMAND_SEEK_TO_NEXT}, {@link #COMMAND_SEEK_TO_MEDIA_ITEM}, {@link #COMMAND_SEEK_BACK}, {@link - * #COMMAND_SEEK_FORWARD}, {@link #COMMAND_SET_SPEED_AND_PITCH}, {@link - * #COMMAND_SET_SHUFFLE_MODE}, {@link #COMMAND_SET_REPEAT_MODE}, {@link - * #COMMAND_GET_CURRENT_MEDIA_ITEM}, {@link #COMMAND_GET_TIMELINE}, {@link - * #COMMAND_GET_MEDIA_ITEMS_METADATA}, {@link #COMMAND_SET_MEDIA_ITEMS_METADATA}, {@link - * #COMMAND_CHANGE_MEDIA_ITEMS}, {@link #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link - * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link - * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link - * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link - * #COMMAND_SET_TRACK_SELECTION_PARAMETERS}, {@link #COMMAND_GET_TRACKS} or {@link - * #COMMAND_SET_MEDIA_ITEM}. + * Commands that indicate which method calls are currently permitted on a particular {@code + * Player} instance, and which corresponding {@link Player.Listener} methods will be invoked. + * + *

      The currently available commands can be inspected with {@link #getAvailableCommands()} and + * {@link #isCommandAvailable(int)}. + * + *

      One of the following values: + * + *

        + *
      • {@link #COMMAND_PLAY_PAUSE} + *
      • {@link #COMMAND_PREPARE} + *
      • {@link #COMMAND_STOP} + *
      • {@link #COMMAND_SEEK_TO_DEFAULT_POSITION} + *
      • {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} + *
      • {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} + *
      • {@link #COMMAND_SEEK_TO_PREVIOUS} + *
      • {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} + *
      • {@link #COMMAND_SEEK_TO_NEXT} + *
      • {@link #COMMAND_SEEK_TO_MEDIA_ITEM} + *
      • {@link #COMMAND_SEEK_BACK} + *
      • {@link #COMMAND_SEEK_FORWARD} + *
      • {@link #COMMAND_SET_SPEED_AND_PITCH} + *
      • {@link #COMMAND_SET_SHUFFLE_MODE} + *
      • {@link #COMMAND_SET_REPEAT_MODE} + *
      • {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} + *
      • {@link #COMMAND_GET_TIMELINE} + *
      • {@link #COMMAND_GET_MEDIA_ITEMS_METADATA} + *
      • {@link #COMMAND_SET_MEDIA_ITEMS_METADATA} + *
      • {@link #COMMAND_SET_MEDIA_ITEM} + *
      • {@link #COMMAND_CHANGE_MEDIA_ITEMS} + *
      • {@link #COMMAND_GET_AUDIO_ATTRIBUTES} + *
      • {@link #COMMAND_GET_VOLUME} + *
      • {@link #COMMAND_GET_DEVICE_VOLUME} + *
      • {@link #COMMAND_SET_VOLUME} + *
      • {@link #COMMAND_SET_DEVICE_VOLUME} + *
      • {@link #COMMAND_ADJUST_DEVICE_VOLUME} + *
      • {@link #COMMAND_SET_VIDEO_SURFACE} + *
      • {@link #COMMAND_GET_TEXT} + *
      • {@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS} + *
      • {@link #COMMAND_GET_TRACKS} + *
      */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // with Kotlin usages from before TYPE_USE was added. @@ -1455,11 +1483,11 @@ public interface Player { int COMMAND_PLAY_PAUSE = 1; /** Command to prepare the player. */ int COMMAND_PREPARE = 2; - /** Command to stop playback or release the player. */ + /** Command to stop playback. */ int COMMAND_STOP = 3; /** Command to seek to the default position of the current {@link MediaItem}. */ int COMMAND_SEEK_TO_DEFAULT_POSITION = 4; - /** Command to seek anywhere into the current {@link MediaItem}. */ + /** Command to seek anywhere inside the current {@link MediaItem}. */ int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; /** * @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead. @@ -1471,7 +1499,10 @@ public interface Player { * @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead. */ @Deprecated int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; - /** Command to seek to an earlier position in the current or previous {@link MediaItem}. */ + /** + * Command to seek to an earlier position in the current {@link MediaItem} or the default position + * of the previous {@link MediaItem}. + */ int COMMAND_SEEK_TO_PREVIOUS = 7; /** Command to seek to the default position of the next {@link MediaItem}. */ int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; @@ -1479,7 +1510,10 @@ public interface Player { * @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead. */ @Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; - /** Command to seek to a later position in the current or next {@link MediaItem}. */ + /** + * Command to seek to a later position in the current {@link MediaItem} or the default position of + * the next {@link MediaItem}. + */ int COMMAND_SEEK_TO_NEXT = 9; /** Command to seek anywhere in any {@link MediaItem}. */ int COMMAND_SEEK_TO_MEDIA_ITEM = 10; @@ -1487,9 +1521,9 @@ public interface Player { * @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead. */ @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM; - /** Command to seek back by a fixed increment into the current {@link MediaItem}. */ + /** Command to seek back by a fixed increment inside the current {@link MediaItem}. */ int COMMAND_SEEK_BACK = 11; - /** Command to seek forward by a fixed increment into the current {@link MediaItem}. */ + /** Command to seek forward by a fixed increment inside the current {@link MediaItem}. */ int COMMAND_SEEK_FORWARD = 12; /** Command to set the playback speed and pitch. */ int COMMAND_SET_SPEED_AND_PITCH = 13; @@ -1501,13 +1535,15 @@ public interface Player { int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; /** Command to get the information about the current timeline. */ int COMMAND_GET_TIMELINE = 17; - /** Command to get the {@link MediaItem MediaItems} metadata. */ + /** Command to get metadata related to the playlist and current {@link MediaItem}. */ + // TODO(b/263132691): Rename this to COMMAND_GET_METADATA int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; - /** Command to set the {@link MediaItem MediaItems} metadata. */ + /** Command to set the playlist metadata. */ + // TODO(b/263132691): Rename this to COMMAND_SET_PLAYLIST_METADATA int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; - /** Command to set a {@link MediaItem MediaItem}. */ + /** Command to set a {@link MediaItem}. */ int COMMAND_SET_MEDIA_ITEM = 31; - /** Command to change the {@link MediaItem MediaItems} in the playlist. */ + /** Command to change the {@linkplain MediaItem media items} in the playlist. */ int COMMAND_CHANGE_MEDIA_ITEMS = 20; /** Command to get the player current {@link AudioAttributes}. */ int COMMAND_GET_AUDIO_ATTRIBUTES = 21; @@ -1517,7 +1553,7 @@ public interface Player { int COMMAND_GET_DEVICE_VOLUME = 23; /** Command to set the player volume. */ int COMMAND_SET_VOLUME = 24; - /** Command to set the device volume and mute it. */ + /** Command to set the device volume. */ int COMMAND_SET_DEVICE_VOLUME = 25; /** Command to increase and decrease the device volume and mute it. */ int COMMAND_ADJUST_DEVICE_VOLUME = 26; @@ -1562,17 +1598,17 @@ public interface Player { void removeListener(Listener listener); /** - * Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to - * the default position. + * Clears the playlist, adds the specified {@linkplain MediaItem media items} and resets the + * position to the default position. * - * @param mediaItems The new {@link MediaItem MediaItems}. + * @param mediaItems The new {@linkplain MediaItem media items}. */ void setMediaItems(List mediaItems); /** - * Clears the playlist and adds the specified {@link MediaItem MediaItems}. + * Clears the playlist and adds the specified {@linkplain MediaItem media items}. * - * @param mediaItems The new {@link MediaItem MediaItems}. + * @param mediaItems The new {@linkplain MediaItem media items}. * @param resetPosition Whether the playback position should be reset to the default position in * the first {@link Timeline.Window}. If false, playback will start from the position defined * by {@link #getCurrentMediaItemIndex()} and {@link #getCurrentPosition()}. @@ -1580,9 +1616,9 @@ public interface Player { void setMediaItems(List mediaItems, boolean resetPosition); /** - * Clears the playlist and adds the specified {@link MediaItem MediaItems}. + * Clears the playlist and adds the specified {@linkplain MediaItem media items}. * - * @param mediaItems The new {@link MediaItem MediaItems}. + * @param mediaItems The new {@linkplain MediaItem media items}. * @param startIndex The {@link MediaItem} index to start playback from. If {@link C#INDEX_UNSET} * is passed, the current position is not reset. * @param startPositionMs The position in milliseconds to start playback from. If {@link @@ -1639,7 +1675,7 @@ public interface Player { /** * Adds a list of media items to the end of the playlist. * - * @param mediaItems The {@link MediaItem MediaItems} to add. + * @param mediaItems The {@linkplain MediaItem media items} to add. */ void addMediaItems(List mediaItems); @@ -1648,7 +1684,7 @@ public interface Player { * * @param index The index at which to add the media items. If the index is larger than the size of * the playlist, the media items are added to the end of the playlist. - * @param mediaItems The {@link MediaItem MediaItems} to add. + * @param mediaItems The {@linkplain MediaItem media items} to add. */ void addMediaItems(int index, List mediaItems); @@ -1745,9 +1781,9 @@ public interface Player { void prepare(); /** - * Returns the current {@link State playback state} of the player. + * Returns the current {@linkplain State playback state} of the player. * - * @return The current {@link State playback state}. + * @return The current {@linkplain State playback state}. * @see Listener#onPlaybackStateChanged(int) */ @State @@ -1757,7 +1793,7 @@ public interface Player { * Returns the reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code * true}, or {@link #PLAYBACK_SUPPRESSION_REASON_NONE} if playback is not suppressed. * - * @return The current {@link PlaybackSuppressionReason playback suppression reason}. + * @return The current {@link PlaybackSuppressionReason}. * @see Listener#onPlaybackSuppressionReasonChanged(int) */ @PlaybackSuppressionReason @@ -1795,11 +1831,11 @@ public interface Player { /** * Resumes playback as soon as {@link #getPlaybackState()} == {@link #STATE_READY}. Equivalent to - * {@code setPlayWhenReady(true)}. + * {@link #setPlayWhenReady(boolean) setPlayWhenReady(true)}. */ void play(); - /** Pauses playback. Equivalent to {@code setPlayWhenReady(false)}. */ + /** Pauses playback. Equivalent to {@link #setPlayWhenReady(boolean) setPlayWhenReady(false)}. */ void pause(); /** @@ -2241,7 +2277,7 @@ public interface Player { @Nullable MediaItem getCurrentMediaItem(); - /** Returns the number of {@link MediaItem media items} in the playlist. */ + /** Returns the number of {@linkplain MediaItem media items} in the playlist. */ int getMediaItemCount(); /** Returns the {@link MediaItem} at the given index. */ @@ -2274,7 +2310,7 @@ public interface Player { /** * Returns an estimate of the total buffered duration from the current position, in milliseconds. - * This includes pre-buffered data for subsequent ads and {@link MediaItem media items}. + * This includes pre-buffered data for subsequent ads and {@linkplain MediaItem media items}. */ long getTotalBufferedDuration(); From de203fe0843f12700c9ac60b4d9786cfadb5c8e9 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 20 Dec 2022 16:30:52 +0000 Subject: [PATCH 062/104] Document the relationship between Player methods and available commands #minor-release PiperOrigin-RevId: 496668378 (cherry picked from commit 54e79689e925a2a18bc02522d4f7f73dcfb35d83) --- .../com/google/android/exoplayer2/Player.java | 579 ++++++++++++++++-- 1 file changed, 524 insertions(+), 55 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 01525cb3b1..3887835b4e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -1403,6 +1403,9 @@ public interface Player { *

      The currently available commands can be inspected with {@link #getAvailableCommands()} and * {@link #isCommandAvailable(int)}. * + *

      See the documentation of each command constant for the details of which methods it permits + * calling. + * *

      One of the following values: * *

        @@ -1479,21 +1482,62 @@ public interface Player { COMMAND_GET_TRACKS, }) @interface Command {} - /** Command to start, pause or resume playback. */ + /** + * Command to start, pause or resume playback. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #play()} + *
        • {@link #pause()} + *
        • {@link #setPlayWhenReady(boolean)} + *
        + */ int COMMAND_PLAY_PAUSE = 1; - /** Command to prepare the player. */ + + /** + * Command to prepare the player. + * + *

        The {@link #prepare()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_PREPARE = 2; - /** Command to stop playback. */ + + /** + * Command to stop playback. + * + *

        The {@link #stop()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_STOP = 3; - /** Command to seek to the default position of the current {@link MediaItem}. */ + + /** + * Command to seek to the default position of the current {@link MediaItem}. + * + *

        The {@link #seekToDefaultPosition()} method must only be called if this command is + * {@linkplain #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_TO_DEFAULT_POSITION = 4; - /** Command to seek anywhere inside the current {@link MediaItem}. */ + + /** + * Command to seek anywhere inside the current {@link MediaItem}. + * + *

        The {@link #seekTo(long)} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; /** * @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead. */ @Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; - /** Command to seek to the default position of the previous {@link MediaItem}. */ + + /** + * Command to seek to the default position of the previous {@link MediaItem}. + * + *

        The {@link #seekToPreviousMediaItem()} method must only be called if this command is + * {@linkplain #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; /** * @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead. @@ -1502,9 +1546,17 @@ public interface Player { /** * Command to seek to an earlier position in the current {@link MediaItem} or the default position * of the previous {@link MediaItem}. + * + *

        The {@link #seekToPrevious()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. */ int COMMAND_SEEK_TO_PREVIOUS = 7; - /** Command to seek to the default position of the next {@link MediaItem}. */ + /** + * Command to seek to the default position of the next {@link MediaItem}. + * + *

        The {@link #seekToNextMediaItem()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; /** * @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead. @@ -1513,57 +1565,259 @@ public interface Player { /** * Command to seek to a later position in the current {@link MediaItem} or the default position of * the next {@link MediaItem}. + * + *

        The {@link #seekToNext()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. */ int COMMAND_SEEK_TO_NEXT = 9; - /** Command to seek anywhere in any {@link MediaItem}. */ + + /** + * Command to seek anywhere in any {@link MediaItem}. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #seekTo(int, long)} + *
        • {@link #seekToDefaultPosition(int)} + *
        + */ int COMMAND_SEEK_TO_MEDIA_ITEM = 10; /** * @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead. */ @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM; - /** Command to seek back by a fixed increment inside the current {@link MediaItem}. */ + /** + * Command to seek back by a fixed increment inside the current {@link MediaItem}. + * + *

        The {@link #seekBack()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_BACK = 11; - /** Command to seek forward by a fixed increment inside the current {@link MediaItem}. */ + /** + * Command to seek forward by a fixed increment inside the current {@link MediaItem}. + * + *

        The {@link #seekForward()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SEEK_FORWARD = 12; - /** Command to set the playback speed and pitch. */ + + /** + * Command to set the playback speed and pitch. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #setPlaybackParameters(PlaybackParameters)} + *
        • {@link #setPlaybackSpeed(float)} + *
        + */ int COMMAND_SET_SPEED_AND_PITCH = 13; - /** Command to enable shuffling. */ + + /** + * Command to enable shuffling. + * + *

        The {@link #setShuffleModeEnabled(boolean)} method must only be called if this command is + * {@linkplain #isCommandAvailable(int) available}. + */ int COMMAND_SET_SHUFFLE_MODE = 14; - /** Command to set the repeat mode. */ + + /** + * Command to set the repeat mode. + * + *

        The {@link #setRepeatMode(int)} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SET_REPEAT_MODE = 15; - /** Command to get the currently playing {@link MediaItem}. */ + + /** + * Command to get the currently playing {@link MediaItem}. + * + *

        The {@link #getCurrentMediaItem()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; - /** Command to get the information about the current timeline. */ + + /** + * Command to get the information about the current timeline. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #getCurrentTimeline()} + *
        • {@link #getCurrentMediaItemIndex()} + *
        • {@link #getCurrentPeriodIndex()} + *
        • {@link #getMediaItemCount()} + *
        • {@link #getMediaItemAt(int)} + *
        • {@link #getNextMediaItemIndex()} + *
        • {@link #getPreviousMediaItemIndex()} + *
        • {@link #hasPreviousMediaItem()} + *
        • {@link #hasNextMediaItem()} + *
        • {@link #getCurrentAdGroupIndex()} + *
        • {@link #getCurrentAdIndexInAdGroup()} + *
        + */ int COMMAND_GET_TIMELINE = 17; - /** Command to get metadata related to the playlist and current {@link MediaItem}. */ + + /** + * Command to get metadata related to the playlist and current {@link MediaItem}. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #getMediaMetadata()} + *
        • {@link #getPlaylistMetadata()} + *
        + */ // TODO(b/263132691): Rename this to COMMAND_GET_METADATA int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; - /** Command to set the playlist metadata. */ + + /** + * Command to set the playlist metadata. + * + *

        The {@link #setPlaylistMetadata(MediaMetadata)} method must only be called if this command + * is {@linkplain #isCommandAvailable(int) available}. + */ // TODO(b/263132691): Rename this to COMMAND_SET_PLAYLIST_METADATA int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; - /** Command to set a {@link MediaItem}. */ + + /** + * Command to set a {@link MediaItem}. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #setMediaItem(MediaItem)} + *
        • {@link #setMediaItem(MediaItem, boolean)} + *
        • {@link #setMediaItem(MediaItem, long)} + *
        + */ int COMMAND_SET_MEDIA_ITEM = 31; - /** Command to change the {@linkplain MediaItem media items} in the playlist. */ + /** + * Command to change the {@linkplain MediaItem media items} in the playlist. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #addMediaItem(MediaItem)} + *
        • {@link #addMediaItem(int, MediaItem)} + *
        • {@link #addMediaItems(List)} + *
        • {@link #addMediaItems(int, List)} + *
        • {@link #clearMediaItems()} + *
        • {@link #moveMediaItem(int, int)} + *
        • {@link #moveMediaItems(int, int, int)} + *
        • {@link #removeMediaItem(int)} + *
        • {@link #removeMediaItems(int, int)} + *
        • {@link #setMediaItems(List)} + *
        • {@link #setMediaItems(List, boolean)} + *
        • {@link #setMediaItems(List, int, long)} + *
        + */ int COMMAND_CHANGE_MEDIA_ITEMS = 20; - /** Command to get the player current {@link AudioAttributes}. */ + + /** + * Command to get the player current {@link AudioAttributes}. + * + *

        The {@link #getAudioAttributes()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_GET_AUDIO_ATTRIBUTES = 21; - /** Command to get the player volume. */ + + /** + * Command to get the player volume. + * + *

        The {@link #getVolume()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_GET_VOLUME = 22; - /** Command to get the device volume and whether it is muted. */ + + /** + * Command to get the device volume and whether it is muted. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #getDeviceVolume()} + *
        • {@link #isDeviceMuted()} + *
        + */ int COMMAND_GET_DEVICE_VOLUME = 23; - /** Command to set the player volume. */ + + /** + * Command to set the player volume. + * + *

        The {@link #setVolume(float)} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SET_VOLUME = 24; - /** Command to set the device volume. */ + /** + * Command to set the device volume. + * + *

        The {@link #setDeviceVolume(int)} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_SET_DEVICE_VOLUME = 25; - /** Command to increase and decrease the device volume and mute it. */ + + /** + * Command to increase and decrease the device volume and mute it. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #increaseDeviceVolume()} + *
        • {@link #decreaseDeviceVolume()} + *
        • {@link #setDeviceMuted(boolean)} + *
        + */ int COMMAND_ADJUST_DEVICE_VOLUME = 26; - /** Command to set and clear the surface on which to render the video. */ + + /** + * Command to set and clear the surface on which to render the video. + * + *

        The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

          + *
        • {@link #setVideoSurface(Surface)} + *
        • {@link #clearVideoSurface()} + *
        • {@link #clearVideoSurface(Surface)} + *
        • {@link #setVideoSurfaceHolder(SurfaceHolder)} + *
        • {@link #clearVideoSurfaceHolder(SurfaceHolder)} + *
        • {@link #setVideoSurfaceView(SurfaceView)} + *
        • {@link #clearVideoSurfaceView(SurfaceView)} + *
        + */ int COMMAND_SET_VIDEO_SURFACE = 27; - /** Command to get the text that should currently be displayed by the player. */ + + /** + * Command to get the text that should currently be displayed by the player. + * + *

        The {@link #getCurrentCues()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_GET_TEXT = 28; - /** Command to set the player's track selection parameters. */ + + /** + * Command to set the player's track selection parameters. + * + *

        The {@link #setTrackSelectionParameters(TrackSelectionParameters)} method must only be + * called if this command is {@linkplain #isCommandAvailable(int) available}. + */ int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29; - /** Command to get details of the current track selection. */ + + /** + * Command to get details of the current track selection. + * + *

        The {@link #getCurrentTracks()} method must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}. + */ int COMMAND_GET_TRACKS = 30; /** Represents an invalid {@link Command}. */ @@ -1601,6 +1855,9 @@ public interface Player { * Clears the playlist, adds the specified {@linkplain MediaItem media items} and resets the * position to the default position. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItems The new {@linkplain MediaItem media items}. */ void setMediaItems(List mediaItems); @@ -1608,6 +1865,9 @@ public interface Player { /** * Clears the playlist and adds the specified {@linkplain MediaItem media items}. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItems The new {@linkplain MediaItem media items}. * @param resetPosition Whether the playback position should be reset to the default position in * the first {@link Timeline.Window}. If false, playback will start from the position defined @@ -1618,6 +1878,9 @@ public interface Player { /** * Clears the playlist and adds the specified {@linkplain MediaItem media items}. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItems The new {@linkplain MediaItem media items}. * @param startIndex The {@link MediaItem} index to start playback from. If {@link C#INDEX_UNSET} * is passed, the current position is not reset. @@ -1634,6 +1897,9 @@ public interface Player { * Clears the playlist, adds the specified {@link MediaItem} and resets the position to the * default position. * + *

        This method must only be called if {@link #COMMAND_SET_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItem The new {@link MediaItem}. */ void setMediaItem(MediaItem mediaItem); @@ -1641,6 +1907,9 @@ public interface Player { /** * Clears the playlist and adds the specified {@link MediaItem}. * + *

        This method must only be called if {@link #COMMAND_SET_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItem The new {@link MediaItem}. * @param startPositionMs The position in milliseconds to start playback from. */ @@ -1649,6 +1918,9 @@ public interface Player { /** * Clears the playlist and adds the specified {@link MediaItem}. * + *

        This method must only be called if {@link #COMMAND_SET_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItem The new {@link MediaItem}. * @param resetPosition Whether the playback position should be reset to the default position. If * false, playback will start from the position defined by {@link #getCurrentMediaItemIndex()} @@ -1659,6 +1931,9 @@ public interface Player { /** * Adds a media item to the end of the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItem The {@link MediaItem} to add. */ void addMediaItem(MediaItem mediaItem); @@ -1666,6 +1941,9 @@ public interface Player { /** * Adds a media item at the given index of the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param index The index at which to add the media item. If the index is larger than the size of * the playlist, the media item is added to the end of the playlist. * @param mediaItem The {@link MediaItem} to add. @@ -1675,6 +1953,9 @@ public interface Player { /** * Adds a list of media items to the end of the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItems The {@linkplain MediaItem media items} to add. */ void addMediaItems(List mediaItems); @@ -1682,6 +1963,9 @@ public interface Player { /** * Adds a list of media items at the given index of the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param index The index at which to add the media items. If the index is larger than the size of * the playlist, the media items are added to the end of the playlist. * @param mediaItems The {@linkplain MediaItem media items} to add. @@ -1691,6 +1975,9 @@ public interface Player { /** * Moves the media item at the current index to the new index. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param currentIndex The current index of the media item to move. If the index is larger than * the size of the playlist, the request is ignored. * @param newIndex The new index of the media item. If the new index is larger than the size of @@ -1701,6 +1988,9 @@ public interface Player { /** * Moves the media item range to the new index. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param fromIndex The start of the range to move. If the index is larger than the size of the * playlist, the request is ignored. * @param toIndex The first item not to be included in the range (exclusive). If the index is @@ -1714,6 +2004,9 @@ public interface Player { /** * Removes the media item at the given index of the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param index The index at which to remove the media item. If the index is larger than the size * of the playlist, the request is ignored. */ @@ -1722,6 +2015,9 @@ public interface Player { /** * Removes a range of media items from the playlist. * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + * * @param fromIndex The index at which to start removing media items. If the index is larger than * the size of the playlist, the request is ignored. * @param toIndex The index of the first item to be kept (exclusive). If the index is larger than @@ -1729,7 +2025,12 @@ public interface Player { */ void removeMediaItems(int fromIndex, int toIndex); - /** Clears the playlist. */ + /** + * Clears the playlist. + * + *

        This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain + * #getAvailableCommands() available}. + */ void clearMediaItems(); /** @@ -1737,13 +2038,6 @@ public interface Player { * *

        This method does not execute the command. * - *

        Executing a command that is not available (for example, calling {@link - * #seekToNextMediaItem()} if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will - * neither throw an exception nor generate a {@link #getPlayerError()} player error}. - * - *

        {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} - * are unavailable if there is no such {@link MediaItem}. - * * @param command A {@link Command}. * @return Whether the {@link Command} is available. * @see Listener#onAvailableCommandsChanged(Commands) @@ -1760,13 +2054,6 @@ public interface Player { * Listener#onAvailableCommandsChanged(Commands)} to get an update when the available commands * change. * - *

        Executing a command that is not available (for example, calling {@link - * #seekToNextMediaItem()} if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will - * neither throw an exception nor generate a {@link #getPlayerError()} player error}. - * - *

        {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} - * are unavailable if there is no such {@link MediaItem}. - * * @return The currently available {@link Commands}. * @see Listener#onAvailableCommandsChanged */ @@ -1775,6 +2062,9 @@ public interface Player { /** * Prepares the player. * + *

        This method must only be called if {@link #COMMAND_PREPARE} is {@linkplain + * #getAvailableCommands() available}. + * *

        This will move the player out of {@link #STATE_IDLE idle state} and the player will start * loading media and acquire resources needed for playback. */ @@ -1832,10 +2122,18 @@ public interface Player { /** * Resumes playback as soon as {@link #getPlaybackState()} == {@link #STATE_READY}. Equivalent to * {@link #setPlayWhenReady(boolean) setPlayWhenReady(true)}. + * + *

        This method must only be called if {@link #COMMAND_PLAY_PAUSE} is {@linkplain + * #getAvailableCommands() available}. */ void play(); - /** Pauses playback. Equivalent to {@link #setPlayWhenReady(boolean) setPlayWhenReady(false)}. */ + /** + * Pauses playback. Equivalent to {@link #setPlayWhenReady(boolean) setPlayWhenReady(false)}. + * + *

        This method must only be called if {@link #COMMAND_PLAY_PAUSE} is {@linkplain + * #getAvailableCommands() available}. + */ void pause(); /** @@ -1843,6 +2141,9 @@ public interface Player { * *

        If the player is already in the ready state then this method pauses and resumes playback. * + *

        This method must only be called if {@link #COMMAND_PLAY_PAUSE} is {@linkplain + * #getAvailableCommands() available}. + * * @param playWhenReady Whether playback should proceed when ready. */ void setPlayWhenReady(boolean playWhenReady); @@ -1858,6 +2159,9 @@ public interface Player { /** * Sets the {@link RepeatMode} to be used for playback. * + *

        This method must only be called if {@link #COMMAND_SET_REPEAT_MODE} is {@linkplain + * #getAvailableCommands() available}. + * * @param repeatMode The repeat mode. */ void setRepeatMode(@RepeatMode int repeatMode); @@ -1874,6 +2178,9 @@ public interface Player { /** * Sets whether shuffling of media items is enabled. * + *

        This method must only be called if {@link #COMMAND_SET_SHUFFLE_MODE} is {@linkplain + * #getAvailableCommands() available}. + * * @param shuffleModeEnabled Whether shuffling is enabled. */ void setShuffleModeEnabled(boolean shuffleModeEnabled); @@ -1897,6 +2204,9 @@ public interface Player { * Seeks to the default position associated with the current {@link MediaItem}. The position can * depend on the type of media being played. For live streams it will typically be the live edge. * For other streams it will typically be the start. + * + *

        This method must only be called if {@link #COMMAND_SEEK_TO_DEFAULT_POSITION} is {@linkplain + * #getAvailableCommands() available}. */ void seekToDefaultPosition(); @@ -1905,6 +2215,9 @@ public interface Player { * depend on the type of media being played. For live streams it will typically be the live edge. * For other streams it will typically be the start. * + *

        This method must only be called if {@link #COMMAND_SEEK_TO_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItemIndex The index of the {@link MediaItem} whose associated default position * should be seeked to. If the index is larger than the size of the playlist, the request is * ignored. @@ -1914,6 +2227,9 @@ public interface Player { /** * Seeks to a position specified in milliseconds in the current {@link MediaItem}. * + *

        This method must only be called if {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} is + * {@linkplain #getAvailableCommands() available}. + * * @param positionMs The seek position in the current {@link MediaItem}, or {@link C#TIME_UNSET} * to seek to the media item's default position. */ @@ -1922,6 +2238,9 @@ public interface Player { /** * Seeks to a position specified in milliseconds in the specified {@link MediaItem}. * + *

        This method must only be called if {@link #COMMAND_SEEK_TO_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @param mediaItemIndex The index of the {@link MediaItem}. If the index is larger than the size * of the playlist, the request is ignored. * @param positionMs The seek position in the specified {@link MediaItem}, or {@link C#TIME_UNSET} @@ -1939,6 +2258,9 @@ public interface Player { /** * Seeks back in the current {@link MediaItem} by {@link #getSeekBackIncrement()} milliseconds. + * + *

        This method must only be called if {@link #COMMAND_SEEK_BACK} is {@linkplain + * #getAvailableCommands() available}. */ void seekBack(); @@ -1953,6 +2275,9 @@ public interface Player { /** * Seeks forward in the current {@link MediaItem} by {@link #getSeekForwardIncrement()} * milliseconds. + * + *

        This method must only be called if {@link #COMMAND_SEEK_FORWARD} is {@linkplain + * #getAvailableCommands() available}. */ void seekForward(); @@ -1998,6 +2323,9 @@ public interface Player { *

        Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

        This method must only be called if {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} is + * {@linkplain #getAvailableCommands() available}. */ void seekToPreviousMediaItem(); @@ -2029,6 +2357,9 @@ public interface Player { * MediaItem}. *

      • Otherwise, seeks to 0 in the current {@link MediaItem}. *
      + * + *

      This method must only be called if {@link #COMMAND_SEEK_TO_PREVIOUS} is {@linkplain + * #getAvailableCommands() available}. */ void seekToPrevious(); @@ -2074,6 +2405,9 @@ public interface Player { *

      Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

      This method must only be called if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ void seekToNextMediaItem(); @@ -2089,6 +2423,9 @@ public interface Player { * has not ended, seeks to the live edge of the current {@link MediaItem}. *

    • Otherwise, does nothing. *
    + * + *

    This method must only be called if {@link #COMMAND_SEEK_TO_NEXT} is {@linkplain + * #getAvailableCommands() available}. */ void seekToNext(); @@ -2100,6 +2437,9 @@ public interface Player { * Listener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the currently * active playback parameters change. * + *

    This method must only be called if {@link #COMMAND_SET_SPEED_AND_PITCH} is {@linkplain + * #getAvailableCommands() available}. + * * @param playbackParameters The playback parameters. */ void setPlaybackParameters(PlaybackParameters playbackParameters); @@ -2110,6 +2450,9 @@ public interface Player { *

    This is equivalent to {@code * setPlaybackParameters(getPlaybackParameters().withSpeed(speed))}. * + *

    This method must only be called if {@link #COMMAND_SET_SPEED_AND_PITCH} is {@linkplain + * #getAvailableCommands() available}. + * * @param speed The linear factor by which playback will be sped up. Must be higher than 0. 1 is * normal speed, 2 is twice as fast, 0.5 is half normal speed. */ @@ -2133,6 +2476,9 @@ public interface Player { * *

    Calling this method does not clear the playlist, reset the playback position or the playback * error. + * + *

    This method must only be called if {@link #COMMAND_STOP} is {@linkplain + * #getAvailableCommands() available}. */ void stop(); @@ -2148,11 +2494,15 @@ public interface Player { * Releases the player. This method must be called when the player is no longer required. The * player must not be used after calling this method. */ + // TODO(b/261158047): Document that COMMAND_RELEASE must be available once it exists. void release(); /** * Returns the current tracks. * + *

    This method must only be called if {@link #COMMAND_GET_TRACKS} is {@linkplain + * #getAvailableCommands() available}. + * * @see Listener#onTracksChanged(Tracks) */ Tracks getCurrentTracks(); @@ -2180,6 +2530,9 @@ public interface Player { * .setMaxVideoSizeSd() * .build()) * } + * + *

    This method must only be called if {@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS} is + * {@linkplain #getAvailableCommands() available}. */ void setTrackSelectionParameters(TrackSelectionParameters parameters); @@ -2192,16 +2545,27 @@ public interface Player { * metadata that has been parsed from the media and output via {@link * Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata}, * it will be prioritised above the same field coming from static or timed metadata. + * + *

    This method must only be called if {@link #COMMAND_GET_MEDIA_ITEMS_METADATA} is {@linkplain + * #getAvailableCommands() available}. */ MediaMetadata getMediaMetadata(); /** * Returns the playlist {@link MediaMetadata}, as set by {@link * #setPlaylistMetadata(MediaMetadata)}, or {@link MediaMetadata#EMPTY} if not supported. + * + *

    This method must only be called if {@link #COMMAND_GET_MEDIA_ITEMS_METADATA} is {@linkplain + * #getAvailableCommands() available}. */ MediaMetadata getPlaylistMetadata(); - /** Sets the playlist {@link MediaMetadata}. */ + /** + * Sets the playlist {@link MediaMetadata}. + * + *

    This method must only be called if {@link #COMMAND_SET_MEDIA_ITEMS_METADATA} is {@linkplain + * #getAvailableCommands() available}. + */ void setPlaylistMetadata(MediaMetadata mediaMetadata); /** @@ -2213,11 +2577,19 @@ public interface Player { /** * Returns the current {@link Timeline}. Never null, but may be empty. * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. + * * @see Listener#onTimelineChanged(Timeline, int) */ Timeline getCurrentTimeline(); - /** Returns the index of the period currently being played. */ + /** + * Returns the index of the period currently being played. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. + */ int getCurrentPeriodIndex(); /** @@ -2230,6 +2602,9 @@ public interface Player { * Returns the index of the current {@link MediaItem} in the {@link #getCurrentTimeline() * timeline}, or the prospective index if the {@link #getCurrentTimeline() current timeline} is * empty. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ int getCurrentMediaItemIndex(); @@ -2248,6 +2623,9 @@ public interface Player { *

    Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ int getNextMediaItemIndex(); @@ -2266,21 +2644,37 @@ public interface Player { *

    Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ int getPreviousMediaItemIndex(); /** * Returns the currently playing {@link MediaItem}. May be null if the timeline is empty. * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @see Listener#onMediaItemTransition(MediaItem, int) */ @Nullable MediaItem getCurrentMediaItem(); - /** Returns the number of {@linkplain MediaItem media items} in the playlist. */ + /** + * Returns the number of {@linkplain MediaItem media items} in the playlist. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. + */ int getMediaItemCount(); - /** Returns the {@link MediaItem} at the given index. */ + /** + * Returns the {@link MediaItem} at the given index. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. + */ MediaItem getMediaItemAt(int index); /** @@ -2375,12 +2769,18 @@ public interface Player { /** * If {@link #isPlayingAd()} returns true, returns the index of the ad group in the period * currently being played. Returns {@link C#INDEX_UNSET} otherwise. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ int getCurrentAdGroupIndex(); /** * If {@link #isPlayingAd()} returns true, returns the index of the ad in its ad group. Returns * {@link C#INDEX_UNSET} otherwise. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ int getCurrentAdIndexInAdGroup(); @@ -2405,13 +2805,21 @@ public interface Player { */ long getContentBufferedPosition(); - /** Returns the attributes for audio playback. */ + /** + * Returns the attributes for audio playback. + * + *

    This method must only be called if {@link #COMMAND_GET_AUDIO_ATTRIBUTES} is {@linkplain + * #getAvailableCommands() available}. + */ AudioAttributes getAudioAttributes(); /** * Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal * unchanged), inclusive. * + *

    This method must only be called if {@link #COMMAND_SET_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + * * @param volume Linear output gain to apply to all audio channels. */ void setVolume(@FloatRange(from = 0, to = 1.0) float volume); @@ -2419,6 +2827,9 @@ public interface Player { /** * Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). * + *

    This method must only be called if {@link #COMMAND_GET_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + * * @return The linear gain applied to all audio channels. */ @FloatRange(from = 0, to = 1.0) @@ -2427,6 +2838,9 @@ public interface Player { /** * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} * currently set on the player. + * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. */ void clearVideoSurface(); @@ -2434,6 +2848,9 @@ public interface Player { * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. * Else does nothing. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surface The surface to clear. */ void clearVideoSurface(@Nullable Surface surface); @@ -2449,6 +2866,9 @@ public interface Player { * this method, since passing the holder allows the player to track the lifecycle of the surface * automatically. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surface The {@link Surface}. */ void setVideoSurface(@Nullable Surface surface); @@ -2460,6 +2880,9 @@ public interface Player { *

    The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread * associated with {@link #getApplicationLooper()}. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surfaceHolder The surface holder. */ void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); @@ -2468,6 +2891,9 @@ public interface Player { * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being * rendered if it matches the one passed. Else does nothing. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surfaceHolder The surface holder to clear. */ void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); @@ -2479,6 +2905,9 @@ public interface Player { *

    The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread * associated with {@link #getApplicationLooper()}. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surfaceView The surface view. */ void setVideoSurfaceView(@Nullable SurfaceView surfaceView); @@ -2487,6 +2916,9 @@ public interface Player { * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed. * Else does nothing. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param surfaceView The texture view to clear. */ void clearVideoSurfaceView(@Nullable SurfaceView surfaceView); @@ -2498,6 +2930,9 @@ public interface Player { *

    The thread that calls the {@link TextureView.SurfaceTextureListener} methods must be the * thread associated with {@link #getApplicationLooper()}. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param textureView The texture view. */ void setVideoTextureView(@Nullable TextureView textureView); @@ -2506,6 +2941,9 @@ public interface Player { * Clears the {@link TextureView} onto which video is being rendered if it matches the one passed. * Else does nothing. * + *

    This method must only be called if {@link #COMMAND_SET_VIDEO_SURFACE} is {@linkplain + * #getAvailableCommands() available}. + * * @param textureView The texture view to clear. */ void clearVideoTextureView(@Nullable TextureView textureView); @@ -2527,7 +2965,12 @@ public interface Player { */ Size getSurfaceSize(); - /** Returns the current {@link CueGroup}. */ + /** + * Returns the current {@link CueGroup}. + * + *

    This method must only be called if {@link #COMMAND_GET_TEXT} is {@linkplain + * #getAvailableCommands() available}. + */ CueGroup getCurrentCues(); /** Gets the device information. */ @@ -2543,26 +2986,52 @@ public interface Player { * *

    For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the * remote device is returned. + * + *

    This method must only be called if {@link #COMMAND_GET_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. */ @IntRange(from = 0) int getDeviceVolume(); - /** Gets whether the device is muted or not. */ + /** + * Gets whether the device is muted or not. + * + *

    This method must only be called if {@link #COMMAND_GET_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + */ boolean isDeviceMuted(); /** * Sets the volume of the device. * + *

    This method must only be called if {@link #COMMAND_SET_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + * * @param volume The volume to set. */ void setDeviceVolume(@IntRange(from = 0) int volume); - /** Increases the volume of the device. */ + /** + * Increases the volume of the device. + * + *

    This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + */ void increaseDeviceVolume(); - /** Decreases the volume of the device. */ + /** + * Decreases the volume of the device. + * + *

    This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + */ void decreaseDeviceVolume(); - /** Sets the mute state of the device. */ + /** + * Sets the mute state of the device. + * + *

    This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain + * #getAvailableCommands() available}. + */ void setDeviceMuted(boolean muted); } From 7afdc9edae41a3540ef5508a6c8e1544235f622b Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 21 Dec 2022 10:52:31 +0000 Subject: [PATCH 063/104] Add error messages to correctness assertions in SimpleBasePlayer Users of this class may run into these assertions when creating the State and they need to check the source code to understand why the State is invalid. Adding error messages to all our correctness assertions helps to understand the root cause more easily. PiperOrigin-RevId: 496875109 (cherry picked from commit b7e887a58dff7615926f8002ee3210b3071c5537) --- .../android/exoplayer2/SimpleBasePlayer.java | 100 +++++++++++------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java index 27d02c9d31..ebb0f25061 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java @@ -552,7 +552,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { public Builder setPlaylist(List playlist) { HashSet uids = new HashSet<>(); for (int i = 0; i < playlist.size(); i++) { - checkArgument(uids.add(playlist.get(i).uid)); + checkArgument(uids.add(playlist.get(i).uid), "Duplicate MediaItemData UID in playlist"); } this.playlist = ImmutableList.copyOf(playlist); this.timeline = new PlaylistTimeline(this.playlist); @@ -885,16 +885,20 @@ public abstract class SimpleBasePlayer extends BasePlayer { if (builder.timeline.isEmpty()) { checkArgument( builder.playbackState == Player.STATE_IDLE - || builder.playbackState == Player.STATE_ENDED); + || builder.playbackState == Player.STATE_ENDED, + "Empty playlist only allowed in STATE_IDLE or STATE_ENDED"); checkArgument( builder.currentAdGroupIndex == C.INDEX_UNSET - && builder.currentAdIndexInAdGroup == C.INDEX_UNSET); + && builder.currentAdIndexInAdGroup == C.INDEX_UNSET, + "Ads not allowed if playlist is empty"); } else { int mediaItemIndex = builder.currentMediaItemIndex; if (mediaItemIndex == C.INDEX_UNSET) { mediaItemIndex = 0; // TODO: Use shuffle order to find first index. } else { - checkArgument(builder.currentMediaItemIndex < builder.timeline.getWindowCount()); + checkArgument( + builder.currentMediaItemIndex < builder.timeline.getWindowCount(), + "currentMediaItemIndex must be less than playlist.size()"); } if (builder.currentAdGroupIndex != C.INDEX_UNSET) { Timeline.Period period = new Timeline.Period(); @@ -907,19 +911,25 @@ public abstract class SimpleBasePlayer extends BasePlayer { getPeriodIndexFromWindowPosition( builder.timeline, mediaItemIndex, contentPositionMs, window, period); builder.timeline.getPeriod(periodIndex, period); - checkArgument(builder.currentAdGroupIndex < period.getAdGroupCount()); + checkArgument( + builder.currentAdGroupIndex < period.getAdGroupCount(), + "PeriodData has less ad groups than adGroupIndex"); int adCountInGroup = period.getAdCountInAdGroup(builder.currentAdGroupIndex); if (adCountInGroup != C.LENGTH_UNSET) { - checkArgument(builder.currentAdIndexInAdGroup < adCountInGroup); + checkArgument( + builder.currentAdIndexInAdGroup < adCountInGroup, + "Ad group has less ads than adIndexInGroupIndex"); } } } if (builder.playerError != null) { - checkArgument(builder.playbackState == Player.STATE_IDLE); + checkArgument( + builder.playbackState == Player.STATE_IDLE, "Player error only allowed in STATE_IDLE"); } if (builder.playbackState == Player.STATE_IDLE || builder.playbackState == Player.STATE_ENDED) { - checkArgument(!builder.isLoading); + checkArgument( + !builder.isLoading, "isLoading only allowed when not in STATE_IDLE or STATE_ENDED"); } PositionSupplier contentPositionMsSupplier = builder.contentPositionMsSupplier; if (builder.contentPositionMs != null) { @@ -1497,9 +1507,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { public Builder setPeriods(List periods) { int periodCount = periods.size(); for (int i = 0; i < periodCount - 1; i++) { - checkArgument(periods.get(i).durationUs != C.TIME_UNSET); + checkArgument( + periods.get(i).durationUs != C.TIME_UNSET, "Periods other than last need a duration"); for (int j = i + 1; j < periodCount; j++) { - checkArgument(!periods.get(i).uid.equals(periods.get(j).uid)); + checkArgument( + !periods.get(i).uid.equals(periods.get(j).uid), + "Duplicate PeriodData UIDs in period list"); } } this.periods = ImmutableList.copyOf(periods); @@ -1578,16 +1591,26 @@ public abstract class SimpleBasePlayer extends BasePlayer { private MediaItemData(Builder builder) { if (builder.liveConfiguration == null) { - checkArgument(builder.presentationStartTimeMs == C.TIME_UNSET); - checkArgument(builder.windowStartTimeMs == C.TIME_UNSET); - checkArgument(builder.elapsedRealtimeEpochOffsetMs == C.TIME_UNSET); + checkArgument( + builder.presentationStartTimeMs == C.TIME_UNSET, + "presentationStartTimeMs can only be set if liveConfiguration != null"); + checkArgument( + builder.windowStartTimeMs == C.TIME_UNSET, + "windowStartTimeMs can only be set if liveConfiguration != null"); + checkArgument( + builder.elapsedRealtimeEpochOffsetMs == C.TIME_UNSET, + "elapsedRealtimeEpochOffsetMs can only be set if liveConfiguration != null"); } else if (builder.presentationStartTimeMs != C.TIME_UNSET && builder.windowStartTimeMs != C.TIME_UNSET) { - checkArgument(builder.windowStartTimeMs >= builder.presentationStartTimeMs); + checkArgument( + builder.windowStartTimeMs >= builder.presentationStartTimeMs, + "windowStartTimeMs can't be less than presentationStartTimeMs"); } int periodCount = builder.periods.size(); if (builder.durationUs != C.TIME_UNSET) { - checkArgument(builder.defaultPositionUs <= builder.durationUs); + checkArgument( + builder.defaultPositionUs <= builder.durationUs, + "defaultPositionUs can't be greater than durationUs"); } this.uid = builder.uid; this.tracks = builder.tracks; @@ -2785,7 +2808,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetPlayWhenReady(boolean playWhenReady) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_PLAY_PAUSE"); } /** @@ -2798,7 +2821,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handlePrepare() { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_PREPARE"); } /** @@ -2811,7 +2834,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleStop() { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_STOP"); } /** @@ -2823,7 +2846,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { // TODO(b/261158047): Add that this method will only be called if COMMAND_RELEASE is available. @ForOverride protected ListenableFuture handleRelease() { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_RELEASE"); } /** @@ -2837,7 +2860,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetRepeatMode(@RepeatMode int repeatMode) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_REPEAT_MODE"); } /** @@ -2851,7 +2874,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_SHUFFLE_MODE"); } /** @@ -2865,7 +2888,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetPlaybackParameters(PlaybackParameters playbackParameters) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_SPEED_AND_PITCH"); } /** @@ -2880,7 +2903,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { @ForOverride protected ListenableFuture handleSetTrackSelectionParameters( TrackSelectionParameters trackSelectionParameters) { - throw new IllegalStateException(); + throw new IllegalStateException( + "Missing implementation to handle COMMAND_SET_TRACK_SELECTION_PARAMETERS"); } /** @@ -2894,7 +2918,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { - throw new IllegalStateException(); + throw new IllegalStateException( + "Missing implementation to handle COMMAND_SET_MEDIA_ITEMS_METADATA"); } /** @@ -2909,7 +2934,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetVolume(@FloatRange(from = 0, to = 1.0) float volume) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_VOLUME"); } /** @@ -2923,7 +2948,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetDeviceVolume(@IntRange(from = 0) int deviceVolume) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_DEVICE_VOLUME"); } /** @@ -2936,7 +2961,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleIncreaseDeviceVolume() { - throw new IllegalStateException(); + throw new IllegalStateException( + "Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME"); } /** @@ -2949,7 +2975,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleDecreaseDeviceVolume() { - throw new IllegalStateException(); + throw new IllegalStateException( + "Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME"); } /** @@ -2963,7 +2990,8 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetDeviceMuted(boolean muted) { - throw new IllegalStateException(); + throw new IllegalStateException( + "Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME"); } /** @@ -2978,7 +3006,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleSetVideoOutput(Object videoOutput) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_VIDEO_SURFACE"); } /** @@ -2995,7 +3023,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_VIDEO_SURFACE"); } /** @@ -3016,7 +3044,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @ForOverride protected ListenableFuture handleSetMediaItems( List mediaItems, int startIndex, long startPositionMs) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_SET_MEDIA_ITEM(S)"); } /** @@ -3032,7 +3060,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS"); } /** @@ -3052,7 +3080,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS"); } /** @@ -3069,7 +3097,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { */ @ForOverride protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS"); } /** @@ -3090,7 +3118,7 @@ public abstract class SimpleBasePlayer extends BasePlayer { @ForOverride protected ListenableFuture handleSeek( int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { - throw new IllegalStateException(); + throw new IllegalStateException("Missing implementation to handle one of the COMMAND_SEEK_*"); } @RequiresNonNull("state") From 3cb0195c185502487bd9b607e67ebaaf31505bab Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 21 Dec 2022 10:58:36 +0000 Subject: [PATCH 064/104] Fix recursive loop when registering controller visibility listeners There are two overloads of this method due to a type 'rename' from `PlayerControlView.VisibilityListener` to `PlayerView.ControllerVisibilityListener`. Currently when you call one overload it passes `null` to the other one (to clear the other listener). Unfortunately this results in it clearing itself, because it receives a null call back! This change tweaks the documentation to clarify that the 'other' listener is only cleared if you pass a non-null listener in. This solves the recursive problem, and allows the 'legacy' visibility listener to be successfully registered. Issue: androidx/media#229 #minor-release PiperOrigin-RevId: 496876397 (cherry picked from commit 37fd65a8e51ddba92584109217114d9f2c2f009d) --- .../android/exoplayer2/ui/StyledPlayerView.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 33b95c087b..48e6821f52 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -868,8 +868,8 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { /** * Sets the {@link StyledPlayerControlView.VisibilityListener}. * - *

    Removes any listener set by {@link - * #setControllerVisibilityListener(StyledPlayerControlView.VisibilityListener)}. + *

    If {@code listener} is non-null then any listener set by {@link + * #setControllerVisibilityListener(StyledPlayerControlView.VisibilityListener)} is removed. * * @param listener The listener to be notified about visibility changes, or null to remove the * current listener. @@ -877,14 +877,16 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { @SuppressWarnings("deprecation") // Clearing the legacy listener. public void setControllerVisibilityListener(@Nullable ControllerVisibilityListener listener) { this.controllerVisibilityListener = listener; - setControllerVisibilityListener((StyledPlayerControlView.VisibilityListener) null); + if (listener != null) { + setControllerVisibilityListener((StyledPlayerControlView.VisibilityListener) null); + } } /** * Sets the {@link StyledPlayerControlView.VisibilityListener}. * - *

    Removes any listener set by {@link - * #setControllerVisibilityListener(ControllerVisibilityListener)}. + *

    If {@code listener} is non-null then any listener set by {@link + * #setControllerVisibilityListener(ControllerVisibilityListener)} is removed. * * @deprecated Use {@link #setControllerVisibilityListener(ControllerVisibilityListener)} instead. */ @@ -903,8 +905,8 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { this.legacyControllerVisibilityListener = listener; if (listener != null) { controller.addVisibilityListener(listener); + setControllerVisibilityListener((ControllerVisibilityListener) null); } - setControllerVisibilityListener((ControllerVisibilityListener) null); } /** From a212bc9bb1ee80a331671921ad682a63050b2445 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 21 Dec 2022 16:00:14 +0000 Subject: [PATCH 065/104] Update migration script Issue: google/ExoPlayer#10854 PiperOrigin-RevId: 496922055 (cherry picked from commit 8e9f83867b5c9536488cf74f06c4cf2535ef848f) From 0417dbae78c53233e97486b39cd2a0b3b98b8ae0 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 21 Dec 2022 18:04:42 +0000 Subject: [PATCH 066/104] Bump IMA SDK version to 3.29.0 Issue: google/ExoPlayer#10845 PiperOrigin-RevId: 496947392 (cherry picked from commit 8ed515880a0b6c05b766e868e2ef931b886925cc) --- extensions/ima/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index e54770f3dc..c9e191d484 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -25,7 +25,7 @@ android { } dependencies { - api 'com.google.ads.interactivemedia.v3:interactivemedia:3.28.1' + api 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0' implementation project(modulePrefix + 'library-core') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion compileOnly 'com.google.errorprone:error_prone_annotations:' + errorProneVersion From e4fa94abe3a89d9e00a30f52c611ef4d89b44713 Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 21 Dec 2022 18:10:19 +0000 Subject: [PATCH 067/104] Check `MediaMetadata` bundle to verify keys are skipped Added another check in test to make sure we don't add keys to bundle for fields with `null` values. PiperOrigin-RevId: 496948705 (cherry picked from commit 890fd0a9fb135e938dc465d936b24065c65dbb98) --- .../google/android/exoplayer2/MediaMetadataTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 8b314fdb81..4073d76b2b 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -107,12 +107,17 @@ public class MediaMetadataTest { } @Test - public void createMinimalMediaMetadata_roundTripViaBundle_yieldsEqualInstance() { + public void toBundleSkipsDefaultValues_fromBundleRestoresThem() { MediaMetadata mediaMetadata = new MediaMetadata.Builder().build(); - MediaMetadata mediaMetadataFromBundle = - MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); + Bundle mediaMetadataBundle = mediaMetadata.toBundle(); + // check Bundle created above, contains no keys. + assertThat(mediaMetadataBundle.keySet()).isEmpty(); + + MediaMetadata mediaMetadataFromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle); + + // check object retrieved from mediaMetadataBundle is equal to mediaMetadata. assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata); // Extras is not implemented in MediaMetadata.equals(Object o). assertThat(mediaMetadataFromBundle.extras).isNull(); From 9b11686ef67481343a4ff1f57e5ab1428e53933d Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 21 Dec 2022 21:35:56 +0000 Subject: [PATCH 068/104] Optimise bundling for `AdPlaybackState` using `AdPlaybackState.NONE` Did not do this optimisation for `AdPlaybackState.AdGroup` as its length is zero for `AdPlaybackState` with no ads. No need to pass default values while fetching keys, which we always set in `AdPlaybackState.AdGroup.toBundle()`. PiperOrigin-RevId: 496995048 (cherry picked from commit f2eac2df711cad579f3042568a24c1f51a119428) --- .../source/ads/AdPlaybackState.java | 32 ++++++++++----- .../source/ads/AdPlaybackStateTest.java | 40 ++++++++++++++++++- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 8d609f507e..719a215244 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -508,9 +508,8 @@ public final class AdPlaybackState implements Bundleable { @SuppressWarnings("nullness:type.argument") private static AdGroup fromBundle(Bundle bundle) { long timeUs = bundle.getLong(keyForField(FIELD_TIME_US)); - int count = bundle.getInt(keyForField(FIELD_COUNT), /* defaultValue= */ C.LENGTH_UNSET); - int originalCount = - bundle.getInt(keyForField(FIELD_ORIGINAL_COUNT), /* defaultValue= */ C.LENGTH_UNSET); + int count = bundle.getInt(keyForField(FIELD_COUNT)); + int originalCount = bundle.getInt(keyForField(FIELD_ORIGINAL_COUNT)); @Nullable ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(keyForField(FIELD_URIS)); @Nullable @@ -1153,10 +1152,18 @@ public final class AdPlaybackState implements Bundleable { for (AdGroup adGroup : adGroups) { adGroupBundleList.add(adGroup.toBundle()); } - bundle.putParcelableArrayList(keyForField(FIELD_AD_GROUPS), adGroupBundleList); - bundle.putLong(keyForField(FIELD_AD_RESUME_POSITION_US), adResumePositionUs); - bundle.putLong(keyForField(FIELD_CONTENT_DURATION_US), contentDurationUs); - bundle.putInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT), removedAdGroupCount); + if (!adGroupBundleList.isEmpty()) { + bundle.putParcelableArrayList(keyForField(FIELD_AD_GROUPS), adGroupBundleList); + } + if (adResumePositionUs != NONE.adResumePositionUs) { + bundle.putLong(keyForField(FIELD_AD_RESUME_POSITION_US), adResumePositionUs); + } + if (contentDurationUs != NONE.contentDurationUs) { + bundle.putLong(keyForField(FIELD_CONTENT_DURATION_US), contentDurationUs); + } + if (removedAdGroupCount != NONE.removedAdGroupCount) { + bundle.putInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT), removedAdGroupCount); + } return bundle; } @@ -1181,10 +1188,15 @@ public final class AdPlaybackState implements Bundleable { } } long adResumePositionUs = - bundle.getLong(keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ 0); + bundle.getLong( + keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ NONE.adResumePositionUs); long contentDurationUs = - bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET); - int removedAdGroupCount = bundle.getInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT)); + bundle.getLong( + keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ NONE.contentDurationUs); + int removedAdGroupCount = + bundle.getInt( + keyForField(FIELD_REMOVED_AD_GROUP_COUNT), + /* defaultValue= */ NONE.removedAdGroupCount); return new AdPlaybackState( /* adsId= */ null, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 114a22f990..3f7e2c7d64 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import android.net.Uri; +import android.os.Bundle; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import org.junit.Assert; @@ -403,7 +404,44 @@ public class AdPlaybackStateTest { } @Test - public void roundTripViaBundle_yieldsEqualFieldsExceptAdsId() { + public void adPlaybackStateWithNoAds_checkValues() { + AdPlaybackState adPlaybackStateWithNoAds = AdPlaybackState.NONE; + + // Please refrain from altering these values since doing so would cause issues with backwards + // compatibility. + assertThat(adPlaybackStateWithNoAds.adsId).isNull(); + assertThat(adPlaybackStateWithNoAds.adGroupCount).isEqualTo(0); + assertThat(adPlaybackStateWithNoAds.adResumePositionUs).isEqualTo(0); + assertThat(adPlaybackStateWithNoAds.contentDurationUs).isEqualTo(C.TIME_UNSET); + assertThat(adPlaybackStateWithNoAds.removedAdGroupCount).isEqualTo(0); + } + + @Test + public void adPlaybackStateWithNoAds_toBundleSkipsDefaultValues_fromBundleRestoresThem() { + AdPlaybackState adPlaybackStateWithNoAds = AdPlaybackState.NONE; + + Bundle adPlaybackStateWithNoAdsBundle = adPlaybackStateWithNoAds.toBundle(); + + // check Bundle created above, contains no keys. + assertThat(adPlaybackStateWithNoAdsBundle.keySet()).isEmpty(); + + AdPlaybackState adPlaybackStateWithNoAdsFromBundle = + AdPlaybackState.CREATOR.fromBundle(adPlaybackStateWithNoAdsBundle); + + // check object retrieved from adPlaybackStateWithNoAdsBundle is equal to AdPlaybackState.NONE + assertThat(adPlaybackStateWithNoAdsFromBundle.adsId).isEqualTo(adPlaybackStateWithNoAds.adsId); + assertThat(adPlaybackStateWithNoAdsFromBundle.adGroupCount) + .isEqualTo(adPlaybackStateWithNoAds.adGroupCount); + assertThat(adPlaybackStateWithNoAdsFromBundle.adResumePositionUs) + .isEqualTo(adPlaybackStateWithNoAds.adResumePositionUs); + assertThat(adPlaybackStateWithNoAdsFromBundle.contentDurationUs) + .isEqualTo(adPlaybackStateWithNoAds.contentDurationUs); + assertThat(adPlaybackStateWithNoAdsFromBundle.removedAdGroupCount) + .isEqualTo(adPlaybackStateWithNoAds.removedAdGroupCount); + } + + @Test + public void createAdPlaybackState_roundTripViaBundle_yieldsEqualFieldsExceptAdsId() { AdPlaybackState originalState = new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US) .withRemovedAdGroupCount(1) From ee72778a426fcccaa75ebf969b20edad82700efc Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 22 Dec 2022 15:25:26 +0000 Subject: [PATCH 069/104] Fix order of playback controls in RTL layout Issue: androidx/media#227 #minor-release PiperOrigin-RevId: 497159283 (cherry picked from commit 8313af1677507fb0cc51d53e81bf930f1a954c87) --- .../ui/src/main/res/layout/exo_styled_player_control_view.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui/src/main/res/layout/exo_styled_player_control_view.xml b/library/ui/src/main/res/layout/exo_styled_player_control_view.xml index a83bb1c72c..f78aac7f75 100644 --- a/library/ui/src/main/res/layout/exo_styled_player_control_view.xml +++ b/library/ui/src/main/res/layout/exo_styled_player_control_view.xml @@ -131,7 +131,8 @@ android:background="@android:color/transparent" android:gravity="center" android:padding="@dimen/exo_styled_controls_padding" - android:clipToPadding="false"> + android:clipToPadding="false" + android:layoutDirection="ltr"> From 1e97da94724f26973f28ad41b51a61bffbac3bd5 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 22 Dec 2022 15:28:27 +0000 Subject: [PATCH 070/104] Enable RTL support in the demo app We might as well keep this enabled by default, rather than having to manually toggle it on to investigate RTL issues like Issue: androidx/media#227. PiperOrigin-RevId: 497159744 (cherry picked from commit 010c6b96e74979370f139f8126c176ea7b279313) --- demos/main/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 5783424b52..36b3729453 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ android:largeHeap="true" android:allowBackup="false" android:requestLegacyExternalStorage="true" + android:supportsRtl="true" android:name="androidx.multidex.MultiDexApplication" tools:targetApi="29"> From 25c964d71d8a8712363d97d3d7aaae764d1d3bd5 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 22 Dec 2022 17:36:53 +0000 Subject: [PATCH 071/104] Remove player listener on the application thread of the player PiperOrigin-RevId: 497183220 (cherry picked from commit 965606f7a7626b95087845191f9b6d4c33ced2e7) --- .../ext/ima/ImaServerSideAdInsertionMediaSource.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index 82762f1df3..8e3cfc6ce7 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -36,6 +36,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.util.Pair; import android.view.ViewGroup; import androidx.annotation.IntDef; @@ -493,7 +494,8 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou this.applicationAdEventListener = applicationAdEventListener; this.applicationAdErrorListener = applicationAdErrorListener; componentListener = new ComponentListener(); - mainHandler = Util.createHandlerForCurrentLooper(); + Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper()); + mainHandler = new Handler(Looper.getMainLooper()); Uri streamRequestUri = checkNotNull(mediaItem.localConfiguration).uri; isLiveStream = ImaServerSideAdInsertionUriBuilder.isLiveStream(streamRequestUri); adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequestUri); @@ -570,8 +572,11 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou super.releaseSourceInternal(); if (loader != null) { loader.release(); - player.removeListener(componentListener); - mainHandler.post(() -> setStreamManager(/* streamManager= */ null)); + mainHandler.post( + () -> { + player.removeListener(componentListener); + setStreamManager(/* streamManager= */ null); + }); loader = null; } } From fea5eea6875be277338407865e1f499c9452b6ca Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 4 Jan 2023 13:51:24 +0000 Subject: [PATCH 072/104] Check bundles in `MediaItem` to verify keys are skipped Added another check in each of these tests to make sure we don't add keys to bundle for fields with default values. Also fixed comments of similar changes in `AdPlaybackStateTest` and `MediaMetadataTest`. PiperOrigin-RevId: 499463581 (cherry picked from commit 9d431a52ef7a39772f65b8de170a225848be5251) --- .../android/exoplayer2/MediaItemTest.java | 27 +++++++++++++++---- .../android/exoplayer2/MediaMetadataTest.java | 3 +-- .../source/ads/AdPlaybackStateTest.java | 3 +-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index c354ceec99..45f91012c9 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -377,12 +377,18 @@ public class MediaItemTest { } @Test - public void createDefaultClippingConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + public void + createDefaultClippingConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() { MediaItem.ClippingConfiguration clippingConfiguration = new MediaItem.ClippingConfiguration.Builder().build(); + Bundle clippingConfigurationBundle = clippingConfiguration.toBundle(); + + // Check that default values are skipped when bundling. + assertThat(clippingConfigurationBundle.keySet()).isEmpty(); + MediaItem.ClippingConfiguration clippingConfigurationFromBundle = - MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfiguration.toBundle()); + MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle); assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration); } @@ -560,12 +566,18 @@ public class MediaItemTest { } @Test - public void createDefaultLiveConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() { + public void + createDefaultLiveConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() { MediaItem.LiveConfiguration liveConfiguration = new MediaItem.LiveConfiguration.Builder().build(); + Bundle liveConfigurationBundle = liveConfiguration.toBundle(); + + // Check that default values are skipped when bundling. + assertThat(liveConfigurationBundle.keySet()).isEmpty(); + MediaItem.LiveConfiguration liveConfigurationFromBundle = - MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfiguration.toBundle()); + MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle); assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration); } @@ -834,9 +846,14 @@ public class MediaItemTest { } @Test - public void createDefaultMediaItemInstance_roundTripViaBundle_yieldsEqualInstance() { + public void createDefaultMediaItemInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() { MediaItem mediaItem = new MediaItem.Builder().build(); + Bundle mediaItemBundle = mediaItem.toBundle(); + + // Check that default values are skipped when bundling. + assertThat(mediaItemBundle.keySet()).isEmpty(); + MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle()); assertThat(mediaItemFromBundle).isEqualTo(mediaItem); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index 4073d76b2b..86d0faa4e5 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -112,12 +112,11 @@ public class MediaMetadataTest { Bundle mediaMetadataBundle = mediaMetadata.toBundle(); - // check Bundle created above, contains no keys. + // Check that default values are skipped when bundling. assertThat(mediaMetadataBundle.keySet()).isEmpty(); MediaMetadata mediaMetadataFromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle); - // check object retrieved from mediaMetadataBundle is equal to mediaMetadata. assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata); // Extras is not implemented in MediaMetadata.equals(Object o). assertThat(mediaMetadataFromBundle.extras).isNull(); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 3f7e2c7d64..63a41f83a3 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -422,13 +422,12 @@ public class AdPlaybackStateTest { Bundle adPlaybackStateWithNoAdsBundle = adPlaybackStateWithNoAds.toBundle(); - // check Bundle created above, contains no keys. + // Check that default values are skipped when bundling. assertThat(adPlaybackStateWithNoAdsBundle.keySet()).isEmpty(); AdPlaybackState adPlaybackStateWithNoAdsFromBundle = AdPlaybackState.CREATOR.fromBundle(adPlaybackStateWithNoAdsBundle); - // check object retrieved from adPlaybackStateWithNoAdsBundle is equal to AdPlaybackState.NONE assertThat(adPlaybackStateWithNoAdsFromBundle.adsId).isEqualTo(adPlaybackStateWithNoAds.adsId); assertThat(adPlaybackStateWithNoAdsFromBundle.adGroupCount) .isEqualTo(adPlaybackStateWithNoAds.adGroupCount); From f3a1f2fa150ed9e7f390e264b141ae288aac51ae Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 4 Jan 2023 17:55:58 +0000 Subject: [PATCH 073/104] Optimise bundling for `Timeline.Window` and `Timeline.Period` Improves the time taken to construct playerInfo from its bundle from ~400 ms to ~300 ms. Also made `Timeline.Window.toBundle(boolean excludeMediaItem)` public as it was required to assert a condition in tests. PiperOrigin-RevId: 499512353 (cherry picked from commit 2a77f1e2f96684c8c2e0b27e4118635af0724811) --- .../google/android/exoplayer2/Timeline.java | 90 ++++++++++++++----- .../android/exoplayer2/TimelineTest.java | 50 ++++++++++- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 086cd0b6ef..8571f870f2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -453,27 +453,59 @@ public abstract class Timeline implements Bundleable { private static final int FIELD_LAST_PERIOD_INDEX = 12; private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13; - private final Bundle toBundle(boolean excludeMediaItem) { + /** + * Returns a {@link Bundle} representing the information stored in this object. + * + *

    It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance + * restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the + * instance will be {@code null}. + * + * @param excludeMediaItem Whether to exclude {@link #mediaItem} of window. + */ + public Bundle toBundle(boolean excludeMediaItem) { Bundle bundle = new Bundle(); - bundle.putBundle( - keyForField(FIELD_MEDIA_ITEM), - excludeMediaItem ? MediaItem.EMPTY.toBundle() : mediaItem.toBundle()); - bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs); - bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs); - bundle.putLong( - keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs); - bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable); - bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic); + if (!excludeMediaItem) { + bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle()); + } + if (presentationStartTimeMs != C.TIME_UNSET) { + bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs); + } + if (windowStartTimeMs != C.TIME_UNSET) { + bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs); + } + if (elapsedRealtimeEpochOffsetMs != C.TIME_UNSET) { + bundle.putLong( + keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs); + } + if (isSeekable) { + bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable); + } + if (isDynamic) { + bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic); + } + @Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration; if (liveConfiguration != null) { bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); } - bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder); - bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs); - bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); - bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex); - bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex); - bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs); + if (isPlaceholder) { + bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder); + } + if (defaultPositionUs != 0) { + bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs); + } + if (durationUs != C.TIME_UNSET) { + bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + } + if (firstPeriodIndex != 0) { + bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex); + } + if (lastPeriodIndex != 0) { + bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex); + } + if (positionInFirstPeriodUs != 0) { + bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs); + } return bundle; } @@ -502,7 +534,7 @@ public abstract class Timeline implements Bundleable { @Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)); @Nullable MediaItem mediaItem = - mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : null; + mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : MediaItem.EMPTY; long presentationStartTimeMs = bundle.getLong( keyForField(FIELD_PRESENTATION_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET); @@ -929,15 +961,24 @@ public abstract class Timeline implements Bundleable { *

    It omits the {@link #id} and {@link #uid} fields so these fields of an instance restored * by {@link #CREATOR} will always be {@code null}. */ - // TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise. @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); - bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); - bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs); - bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder); - bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle()); + if (windowIndex != 0) { + bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); + } + if (durationUs != C.TIME_UNSET) { + bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + } + if (positionInWindowUs != 0) { + bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs); + } + if (isPlaceholder) { + bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder); + } + if (!adPlaybackState.equals(AdPlaybackState.NONE)) { + bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle()); + } return bundle; } @@ -954,7 +995,8 @@ public abstract class Timeline implements Bundleable { bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); long positionInWindowUs = bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0); - boolean isPlaceholder = bundle.getBoolean(keyForField(FIELD_PLACEHOLDER)); + boolean isPlaceholder = + bundle.getBoolean(keyForField(FIELD_PLACEHOLDER), /* defaultValue= */ false); @Nullable Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE)); AdPlaybackState adPlaybackState = diff --git a/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java index 3e5bc2f75c..57fffe609e 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2; import static com.google.common.truth.Truth.assertThat; +import android.os.Bundle; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.MediaItem.LiveConfiguration; @@ -268,7 +269,9 @@ public class TimelineTest { /* durationUs= */ 2, /* defaultPositionUs= */ 22, /* windowOffsetInFirstPeriodUs= */ 222, - ImmutableList.of(AdPlaybackState.NONE), + ImmutableList.of( + new AdPlaybackState( + /* adsId= */ null, /* adGroupTimesUs...= */ 10_000, 20_000)), new MediaItem.Builder().setMediaId("mediaId2").build()), new TimelineWindowDefinition( /* periodCount= */ 3, @@ -335,6 +338,31 @@ public class TimelineTest { TimelineAsserts.assertEmpty(Timeline.CREATOR.fromBundle(Timeline.EMPTY.toBundle())); } + @Test + public void window_toBundleSkipsDefaultValues_fromBundleRestoresThem() { + Timeline.Window window = new Timeline.Window(); + // Please refrain from altering these default values since doing so would cause issues with + // backwards compatibility. + window.presentationStartTimeMs = C.TIME_UNSET; + window.windowStartTimeMs = C.TIME_UNSET; + window.elapsedRealtimeEpochOffsetMs = C.TIME_UNSET; + window.durationUs = C.TIME_UNSET; + window.mediaItem = new MediaItem.Builder().build(); + + Bundle windowBundle = window.toBundle(); + + // Check that default values are skipped when bundling. MediaItem key is not added to the bundle + // only when excludeMediaItem is true. + assertThat(windowBundle.keySet()).hasSize(1); + assertThat(window.toBundle(/* excludeMediaItem= */ true).keySet()).isEmpty(); + + Timeline.Window restoredWindow = Timeline.Window.CREATOR.fromBundle(windowBundle); + + assertThat(restoredWindow.manifest).isNull(); + TimelineAsserts.assertWindowEqualsExceptUidAndManifest( + /* expectedWindow= */ window, /* actualWindow= */ restoredWindow); + } + @Test public void roundTripViaBundle_ofWindow_yieldsEqualInstanceExceptUidAndManifest() { Timeline.Window window = new Timeline.Window(); @@ -368,6 +396,26 @@ public class TimelineTest { /* expectedWindow= */ window, /* actualWindow= */ restoredWindow); } + @Test + public void period_toBundleSkipsDefaultValues_fromBundleRestoresThem() { + Timeline.Period period = new Timeline.Period(); + // Please refrain from altering these default values since doing so would cause issues with + // backwards compatibility. + period.durationUs = C.TIME_UNSET; + + Bundle periodBundle = period.toBundle(); + + // Check that default values are skipped when bundling. + assertThat(periodBundle.keySet()).isEmpty(); + + Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(periodBundle); + + assertThat(restoredPeriod.id).isNull(); + assertThat(restoredPeriod.uid).isNull(); + TimelineAsserts.assertPeriodEqualsExceptIds( + /* expectedPeriod= */ period, /* actualPeriod= */ restoredPeriod); + } + @Test public void roundTripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() { Timeline.Period period = new Timeline.Period(); From 25e6f628d3431847c5607d0f9e442ba905c143bb Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 4 Jan 2023 18:35:06 +0000 Subject: [PATCH 074/104] Throw a ParserException instead of a NullPointerException if the sample table (stbl) is missing a required sample description (stsd). As per the javadoc for AtomParsers.parseTrack, ParserException should be "thrown if the trak atom can't be parsed." PiperOrigin-RevId: 499522748 (cherry picked from commit bbe78b10c125529d039fea9bf0117cc04d2a3532) --- .../android/exoplayer2/extractor/mp4/AtomParsers.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index ec5c6fdd92..3a9cedf98a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.extractor.ExtractorUtil; import com.google.android.exoplayer2.extractor.GaplessInfoHolder; +import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; @@ -308,9 +309,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; Pair mdhdData = parseMdhd(checkNotNull(mdia.getLeafAtomOfType(Atom.TYPE_mdhd)).data); + LeafAtom stsd = stbl.getLeafAtomOfType(Atom.TYPE_stsd); + if (stsd == null) { + throw ParserException.createForMalformedContainer( + "Malformed sample table (stbl) missing sample description (stsd)", /* cause= */ null); + } StsdData stsdData = parseStsd( - checkNotNull(stbl.getLeafAtomOfType(Atom.TYPE_stsd)).data, + stsd.data, tkhdData.id, tkhdData.rotationDegrees, mdhdData.second, From f9ae0a6298d5d906584460a3c5ceb9590d79135d Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 5 Jan 2023 17:20:43 +0000 Subject: [PATCH 075/104] Fix typo in `DefaultTrackSelector.Parameters` field PiperOrigin-RevId: 499905136 (cherry picked from commit bdd6818c14f97ba305d008c963fd2c369fab312c) --- .../exoplayer2/trackselection/DefaultTrackSelector.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 02c2fc191d..48c117194d 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 @@ -841,7 +841,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Audio setExceedAudioConstraintsIfNecessary( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY), + Parameters.keyForField(Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY), defaultValue.exceedAudioConstraintsIfNecessary)); setAllowAudioMixedMimeTypeAdaptiveness( bundle.getBoolean( @@ -1874,7 +1874,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private static final int FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS = FIELD_CUSTOM_ID_BASE + 1; private static final int FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS = FIELD_CUSTOM_ID_BASE + 2; - private static final int FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY = FIELD_CUSTOM_ID_BASE + 3; + private static final int FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY = FIELD_CUSTOM_ID_BASE + 3; private static final int FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS = FIELD_CUSTOM_ID_BASE + 4; private static final int FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS = @@ -1916,7 +1916,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { allowVideoMixedDecoderSupportAdaptiveness); // Audio bundle.putBoolean( - keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY), + keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY), exceedAudioConstraintsIfNecessary); bundle.putBoolean( keyForField(FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS), From 0b95e2ca01a4503d5745c972d752130ea59933d5 Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 5 Jan 2023 22:04:57 +0000 Subject: [PATCH 076/104] Initialise fields used for bundling as String directly Initialising the fields as Integer and then getting a String on compute time is slow. Instead we directly initialise these fields as String. Improves the time taken in bundling PlayerInfo further to less than 200ms from ~300ms. Also modified a test to improve productive coverage. PiperOrigin-RevId: 500003935 (cherry picked from commit d49437c6e112e60e9c85edeef73a73bce7de939a) --- .../ImaServerSideAdInsertionMediaSource.java | 23 +- .../google/android/exoplayer2/DeviceInfo.java | 30 +- .../com/google/android/exoplayer2/Format.java | 235 ++++++--------- .../android/exoplayer2/HeartRating.java | 35 +-- .../google/android/exoplayer2/MediaItem.java | 185 ++++-------- .../android/exoplayer2/MediaMetadata.java | 279 ++++++++---------- .../android/exoplayer2/PercentageRating.java | 29 +- .../android/exoplayer2/PlaybackException.java | 44 +-- .../exoplayer2/PlaybackParameters.java | 29 +- .../com/google/android/exoplayer2/Player.java | 80 ++--- .../com/google/android/exoplayer2/Rating.java | 16 +- .../google/android/exoplayer2/StarRating.java | 37 +-- .../android/exoplayer2/ThumbRating.java | 36 +-- .../google/android/exoplayer2/Timeline.java | 208 ++++--------- .../com/google/android/exoplayer2/Tracks.java | 67 +---- .../exoplayer2/audio/AudioAttributes.java | 64 ++-- .../android/exoplayer2/source/TrackGroup.java | 31 +- .../source/ads/AdPlaybackState.java | 113 +++---- .../google/android/exoplayer2/text/Cue.java | 154 ++++------ .../android/exoplayer2/text/CueGroup.java | 30 +- .../TrackSelectionOverride.java | 29 +- .../TrackSelectionParameters.java | 201 +++++-------- .../google/android/exoplayer2/util/Util.java | 9 + .../android/exoplayer2/video/ColorInfo.java | 47 +-- .../android/exoplayer2/video/VideoSize.java | 48 +-- .../android/exoplayer2/MediaItemTest.java | 5 + .../exoplayer2/ExoPlaybackException.java | 43 +-- .../exoplayer2/source/TrackGroupArray.java | 27 +- .../trackselection/DefaultTrackSelector.java | 179 +++++------ 29 files changed, 796 insertions(+), 1517 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index 8e3cfc6ce7..ea307b294b 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -30,7 +30,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.usToMs; -import static java.lang.annotation.ElementType.TYPE_USE; import android.content.Context; import android.net.Uri; @@ -39,7 +38,6 @@ import android.os.Handler; import android.os.Looper; import android.util.Pair; import android.view.ViewGroup; -import androidx.annotation.IntDef; import androidx.annotation.MainThread; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -97,10 +95,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -326,13 +320,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_AD_PLAYBACK_STATES}) - private @interface FieldNumber {} - - private static final int FIELD_AD_PLAYBACK_STATES = 1; + private static final String FIELD_AD_PLAYBACK_STATES = Util.intToStringMaxRadix(1); @Override public Bundle toBundle() { @@ -341,7 +329,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou for (Map.Entry entry : adPlaybackStates.entrySet()) { adPlaybackStatesBundle.putBundle(entry.getKey(), entry.getValue().toBundle()); } - bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATES), adPlaybackStatesBundle); + bundle.putBundle(FIELD_AD_PLAYBACK_STATES, adPlaybackStatesBundle); return bundle; } @@ -352,8 +340,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou @Nullable ImmutableMap.Builder adPlaybackStateMap = new ImmutableMap.Builder<>(); - Bundle adPlaybackStateBundle = - checkNotNull(bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATES))); + Bundle adPlaybackStateBundle = checkNotNull(bundle.getBundle(FIELD_AD_PLAYBACK_STATES)); for (String key : adPlaybackStateBundle.keySet()) { AdPlaybackState adPlaybackState = AdPlaybackState.CREATOR.fromBundle( @@ -363,10 +350,6 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou } return new State(adPlaybackStateMap.buildOrThrow()); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } private final ImaUtil.ServerSideAdInsertionConfiguration configuration; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/DeviceInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/DeviceInfo.java index c0f8931c29..4ffc59adbd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/DeviceInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/DeviceInfo.java @@ -20,6 +20,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -85,22 +86,16 @@ public final class DeviceInfo implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_PLAYBACK_TYPE, FIELD_MIN_VOLUME, FIELD_MAX_VOLUME}) - private @interface FieldNumber {} - - private static final int FIELD_PLAYBACK_TYPE = 0; - private static final int FIELD_MIN_VOLUME = 1; - private static final int FIELD_MAX_VOLUME = 2; + private static final String FIELD_PLAYBACK_TYPE = Util.intToStringMaxRadix(0); + private static final String FIELD_MIN_VOLUME = Util.intToStringMaxRadix(1); + private static final String FIELD_MAX_VOLUME = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_PLAYBACK_TYPE), playbackType); - bundle.putInt(keyForField(FIELD_MIN_VOLUME), minVolume); - bundle.putInt(keyForField(FIELD_MAX_VOLUME), maxVolume); + bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType); + bundle.putInt(FIELD_MIN_VOLUME, minVolume); + bundle.putInt(FIELD_MAX_VOLUME, maxVolume); return bundle; } @@ -108,14 +103,9 @@ public final class DeviceInfo implements Bundleable { public static final Creator CREATOR = bundle -> { int playbackType = - bundle.getInt( - keyForField(FIELD_PLAYBACK_TYPE), /* defaultValue= */ PLAYBACK_TYPE_LOCAL); - int minVolume = bundle.getInt(keyForField(FIELD_MIN_VOLUME), /* defaultValue= */ 0); - int maxVolume = bundle.getInt(keyForField(FIELD_MAX_VOLUME), /* defaultValue= */ 0); + bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL); + int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0); + int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0); return new DeviceInfo(playbackType, minVolume, maxVolume); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index e55d17490a..5ad355ce2d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -15,10 +15,7 @@ */ package com.google.android.exoplayer2; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; @@ -28,10 +25,6 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.base.Joiner; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1456,73 +1449,37 @@ public final class Format implements Bundleable { } // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_ID, - FIELD_LABEL, - FIELD_LANGUAGE, - FIELD_SELECTION_FLAGS, - FIELD_ROLE_FLAGS, - FIELD_AVERAGE_BITRATE, - FIELD_PEAK_BITRATE, - FIELD_CODECS, - FIELD_METADATA, - FIELD_CONTAINER_MIME_TYPE, - FIELD_SAMPLE_MIME_TYPE, - FIELD_MAX_INPUT_SIZE, - FIELD_INITIALIZATION_DATA, - FIELD_DRM_INIT_DATA, - FIELD_SUBSAMPLE_OFFSET_US, - FIELD_WIDTH, - FIELD_HEIGHT, - FIELD_FRAME_RATE, - FIELD_ROTATION_DEGREES, - FIELD_PIXEL_WIDTH_HEIGHT_RATIO, - FIELD_PROJECTION_DATA, - FIELD_STEREO_MODE, - FIELD_COLOR_INFO, - FIELD_CHANNEL_COUNT, - FIELD_SAMPLE_RATE, - FIELD_PCM_ENCODING, - FIELD_ENCODER_DELAY, - FIELD_ENCODER_PADDING, - FIELD_ACCESSIBILITY_CHANNEL, - FIELD_CRYPTO_TYPE, - }) - private @interface FieldNumber {} - private static final int FIELD_ID = 0; - private static final int FIELD_LABEL = 1; - private static final int FIELD_LANGUAGE = 2; - private static final int FIELD_SELECTION_FLAGS = 3; - private static final int FIELD_ROLE_FLAGS = 4; - private static final int FIELD_AVERAGE_BITRATE = 5; - private static final int FIELD_PEAK_BITRATE = 6; - private static final int FIELD_CODECS = 7; - private static final int FIELD_METADATA = 8; - private static final int FIELD_CONTAINER_MIME_TYPE = 9; - private static final int FIELD_SAMPLE_MIME_TYPE = 10; - private static final int FIELD_MAX_INPUT_SIZE = 11; - private static final int FIELD_INITIALIZATION_DATA = 12; - private static final int FIELD_DRM_INIT_DATA = 13; - private static final int FIELD_SUBSAMPLE_OFFSET_US = 14; - private static final int FIELD_WIDTH = 15; - private static final int FIELD_HEIGHT = 16; - private static final int FIELD_FRAME_RATE = 17; - private static final int FIELD_ROTATION_DEGREES = 18; - private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 19; - private static final int FIELD_PROJECTION_DATA = 20; - private static final int FIELD_STEREO_MODE = 21; - private static final int FIELD_COLOR_INFO = 22; - private static final int FIELD_CHANNEL_COUNT = 23; - private static final int FIELD_SAMPLE_RATE = 24; - private static final int FIELD_PCM_ENCODING = 25; - private static final int FIELD_ENCODER_DELAY = 26; - private static final int FIELD_ENCODER_PADDING = 27; - private static final int FIELD_ACCESSIBILITY_CHANNEL = 28; - private static final int FIELD_CRYPTO_TYPE = 29; + private static final String FIELD_ID = Util.intToStringMaxRadix(0); + private static final String FIELD_LABEL = Util.intToStringMaxRadix(1); + private static final String FIELD_LANGUAGE = Util.intToStringMaxRadix(2); + private static final String FIELD_SELECTION_FLAGS = Util.intToStringMaxRadix(3); + private static final String FIELD_ROLE_FLAGS = Util.intToStringMaxRadix(4); + private static final String FIELD_AVERAGE_BITRATE = Util.intToStringMaxRadix(5); + private static final String FIELD_PEAK_BITRATE = Util.intToStringMaxRadix(6); + private static final String FIELD_CODECS = Util.intToStringMaxRadix(7); + private static final String FIELD_METADATA = Util.intToStringMaxRadix(8); + private static final String FIELD_CONTAINER_MIME_TYPE = Util.intToStringMaxRadix(9); + private static final String FIELD_SAMPLE_MIME_TYPE = Util.intToStringMaxRadix(10); + private static final String FIELD_MAX_INPUT_SIZE = Util.intToStringMaxRadix(11); + private static final String FIELD_INITIALIZATION_DATA = Util.intToStringMaxRadix(12); + private static final String FIELD_DRM_INIT_DATA = Util.intToStringMaxRadix(13); + private static final String FIELD_SUBSAMPLE_OFFSET_US = Util.intToStringMaxRadix(14); + private static final String FIELD_WIDTH = Util.intToStringMaxRadix(15); + private static final String FIELD_HEIGHT = Util.intToStringMaxRadix(16); + private static final String FIELD_FRAME_RATE = Util.intToStringMaxRadix(17); + private static final String FIELD_ROTATION_DEGREES = Util.intToStringMaxRadix(18); + private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(19); + private static final String FIELD_PROJECTION_DATA = Util.intToStringMaxRadix(20); + private static final String FIELD_STEREO_MODE = Util.intToStringMaxRadix(21); + private static final String FIELD_COLOR_INFO = Util.intToStringMaxRadix(22); + private static final String FIELD_CHANNEL_COUNT = Util.intToStringMaxRadix(23); + private static final String FIELD_SAMPLE_RATE = Util.intToStringMaxRadix(24); + private static final String FIELD_PCM_ENCODING = Util.intToStringMaxRadix(25); + private static final String FIELD_ENCODER_DELAY = Util.intToStringMaxRadix(26); + private static final String FIELD_ENCODER_PADDING = Util.intToStringMaxRadix(27); + private static final String FIELD_ACCESSIBILITY_CHANNEL = Util.intToStringMaxRadix(28); + private static final String FIELD_CRYPTO_TYPE = Util.intToStringMaxRadix(29); @Override public Bundle toBundle() { @@ -1535,51 +1492,51 @@ public final class Format implements Bundleable { */ public Bundle toBundle(boolean excludeMetadata) { Bundle bundle = new Bundle(); - bundle.putString(keyForField(FIELD_ID), id); - bundle.putString(keyForField(FIELD_LABEL), label); - bundle.putString(keyForField(FIELD_LANGUAGE), language); - bundle.putInt(keyForField(FIELD_SELECTION_FLAGS), selectionFlags); - bundle.putInt(keyForField(FIELD_ROLE_FLAGS), roleFlags); - bundle.putInt(keyForField(FIELD_AVERAGE_BITRATE), averageBitrate); - bundle.putInt(keyForField(FIELD_PEAK_BITRATE), peakBitrate); - bundle.putString(keyForField(FIELD_CODECS), codecs); + bundle.putString(FIELD_ID, id); + bundle.putString(FIELD_LABEL, label); + bundle.putString(FIELD_LANGUAGE, language); + bundle.putInt(FIELD_SELECTION_FLAGS, selectionFlags); + bundle.putInt(FIELD_ROLE_FLAGS, roleFlags); + bundle.putInt(FIELD_AVERAGE_BITRATE, averageBitrate); + bundle.putInt(FIELD_PEAK_BITRATE, peakBitrate); + bundle.putString(FIELD_CODECS, codecs); if (!excludeMetadata) { // TODO (internal ref: b/239701618) - bundle.putParcelable(keyForField(FIELD_METADATA), metadata); + bundle.putParcelable(FIELD_METADATA, metadata); } // Container specific. - bundle.putString(keyForField(FIELD_CONTAINER_MIME_TYPE), containerMimeType); + bundle.putString(FIELD_CONTAINER_MIME_TYPE, containerMimeType); // Sample specific. - bundle.putString(keyForField(FIELD_SAMPLE_MIME_TYPE), sampleMimeType); - bundle.putInt(keyForField(FIELD_MAX_INPUT_SIZE), maxInputSize); + bundle.putString(FIELD_SAMPLE_MIME_TYPE, sampleMimeType); + bundle.putInt(FIELD_MAX_INPUT_SIZE, maxInputSize); for (int i = 0; i < initializationData.size(); i++) { bundle.putByteArray(keyForInitializationData(i), initializationData.get(i)); } // DrmInitData doesn't need to be Bundleable as it's only used in the playing process to // initialize the decoder. - bundle.putParcelable(keyForField(FIELD_DRM_INIT_DATA), drmInitData); - bundle.putLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), subsampleOffsetUs); + bundle.putParcelable(FIELD_DRM_INIT_DATA, drmInitData); + bundle.putLong(FIELD_SUBSAMPLE_OFFSET_US, subsampleOffsetUs); // Video specific. - bundle.putInt(keyForField(FIELD_WIDTH), width); - bundle.putInt(keyForField(FIELD_HEIGHT), height); - bundle.putFloat(keyForField(FIELD_FRAME_RATE), frameRate); - bundle.putInt(keyForField(FIELD_ROTATION_DEGREES), rotationDegrees); - bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); - bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData); - bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode); + bundle.putInt(FIELD_WIDTH, width); + bundle.putInt(FIELD_HEIGHT, height); + bundle.putFloat(FIELD_FRAME_RATE, frameRate); + bundle.putInt(FIELD_ROTATION_DEGREES, rotationDegrees); + bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio); + bundle.putByteArray(FIELD_PROJECTION_DATA, projectionData); + bundle.putInt(FIELD_STEREO_MODE, stereoMode); if (colorInfo != null) { - bundle.putBundle(keyForField(FIELD_COLOR_INFO), colorInfo.toBundle()); + bundle.putBundle(FIELD_COLOR_INFO, colorInfo.toBundle()); } // Audio specific. - bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount); - bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate); - bundle.putInt(keyForField(FIELD_PCM_ENCODING), pcmEncoding); - bundle.putInt(keyForField(FIELD_ENCODER_DELAY), encoderDelay); - bundle.putInt(keyForField(FIELD_ENCODER_PADDING), encoderPadding); + bundle.putInt(FIELD_CHANNEL_COUNT, channelCount); + bundle.putInt(FIELD_SAMPLE_RATE, sampleRate); + bundle.putInt(FIELD_PCM_ENCODING, pcmEncoding); + bundle.putInt(FIELD_ENCODER_DELAY, encoderDelay); + bundle.putInt(FIELD_ENCODER_PADDING, encoderPadding); // Text specific. - bundle.putInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), accessibilityChannel); + bundle.putInt(FIELD_ACCESSIBILITY_CHANNEL, accessibilityChannel); // Source specific. - bundle.putInt(keyForField(FIELD_CRYPTO_TYPE), cryptoType); + bundle.putInt(FIELD_CRYPTO_TYPE, cryptoType); return bundle; } @@ -1590,28 +1547,22 @@ public final class Format implements Bundleable { Builder builder = new Builder(); BundleableUtil.ensureClassLoader(bundle); builder - .setId(defaultIfNull(bundle.getString(keyForField(FIELD_ID)), DEFAULT.id)) - .setLabel(defaultIfNull(bundle.getString(keyForField(FIELD_LABEL)), DEFAULT.label)) - .setLanguage(defaultIfNull(bundle.getString(keyForField(FIELD_LANGUAGE)), DEFAULT.language)) - .setSelectionFlags( - bundle.getInt(keyForField(FIELD_SELECTION_FLAGS), DEFAULT.selectionFlags)) - .setRoleFlags(bundle.getInt(keyForField(FIELD_ROLE_FLAGS), DEFAULT.roleFlags)) - .setAverageBitrate( - bundle.getInt(keyForField(FIELD_AVERAGE_BITRATE), DEFAULT.averageBitrate)) - .setPeakBitrate(bundle.getInt(keyForField(FIELD_PEAK_BITRATE), DEFAULT.peakBitrate)) - .setCodecs(defaultIfNull(bundle.getString(keyForField(FIELD_CODECS)), DEFAULT.codecs)) - .setMetadata( - defaultIfNull(bundle.getParcelable(keyForField(FIELD_METADATA)), DEFAULT.metadata)) + .setId(defaultIfNull(bundle.getString(FIELD_ID), DEFAULT.id)) + .setLabel(defaultIfNull(bundle.getString(FIELD_LABEL), DEFAULT.label)) + .setLanguage(defaultIfNull(bundle.getString(FIELD_LANGUAGE), DEFAULT.language)) + .setSelectionFlags(bundle.getInt(FIELD_SELECTION_FLAGS, DEFAULT.selectionFlags)) + .setRoleFlags(bundle.getInt(FIELD_ROLE_FLAGS, DEFAULT.roleFlags)) + .setAverageBitrate(bundle.getInt(FIELD_AVERAGE_BITRATE, DEFAULT.averageBitrate)) + .setPeakBitrate(bundle.getInt(FIELD_PEAK_BITRATE, DEFAULT.peakBitrate)) + .setCodecs(defaultIfNull(bundle.getString(FIELD_CODECS), DEFAULT.codecs)) + .setMetadata(defaultIfNull(bundle.getParcelable(FIELD_METADATA), DEFAULT.metadata)) // Container specific. .setContainerMimeType( - defaultIfNull( - bundle.getString(keyForField(FIELD_CONTAINER_MIME_TYPE)), - DEFAULT.containerMimeType)) + defaultIfNull(bundle.getString(FIELD_CONTAINER_MIME_TYPE), DEFAULT.containerMimeType)) // Sample specific. .setSampleMimeType( - defaultIfNull( - bundle.getString(keyForField(FIELD_SAMPLE_MIME_TYPE)), DEFAULT.sampleMimeType)) - .setMaxInputSize(bundle.getInt(keyForField(FIELD_MAX_INPUT_SIZE), DEFAULT.maxInputSize)); + defaultIfNull(bundle.getString(FIELD_SAMPLE_MIME_TYPE), DEFAULT.sampleMimeType)) + .setMaxInputSize(bundle.getInt(FIELD_MAX_INPUT_SIZE, DEFAULT.maxInputSize)); List initializationData = new ArrayList<>(); for (int i = 0; ; i++) { @@ -1623,47 +1574,39 @@ public final class Format implements Bundleable { } builder .setInitializationData(initializationData) - .setDrmInitData(bundle.getParcelable(keyForField(FIELD_DRM_INIT_DATA))) - .setSubsampleOffsetUs( - bundle.getLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), DEFAULT.subsampleOffsetUs)) + .setDrmInitData(bundle.getParcelable(FIELD_DRM_INIT_DATA)) + .setSubsampleOffsetUs(bundle.getLong(FIELD_SUBSAMPLE_OFFSET_US, DEFAULT.subsampleOffsetUs)) // Video specific. - .setWidth(bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT.width)) - .setHeight(bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT.height)) - .setFrameRate(bundle.getFloat(keyForField(FIELD_FRAME_RATE), DEFAULT.frameRate)) - .setRotationDegrees( - bundle.getInt(keyForField(FIELD_ROTATION_DEGREES), DEFAULT.rotationDegrees)) + .setWidth(bundle.getInt(FIELD_WIDTH, DEFAULT.width)) + .setHeight(bundle.getInt(FIELD_HEIGHT, DEFAULT.height)) + .setFrameRate(bundle.getFloat(FIELD_FRAME_RATE, DEFAULT.frameRate)) + .setRotationDegrees(bundle.getInt(FIELD_ROTATION_DEGREES, DEFAULT.rotationDegrees)) .setPixelWidthHeightRatio( - bundle.getFloat( - keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio)) - .setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA))) - .setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode)); - Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO)); + bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT.pixelWidthHeightRatio)) + .setProjectionData(bundle.getByteArray(FIELD_PROJECTION_DATA)) + .setStereoMode(bundle.getInt(FIELD_STEREO_MODE, DEFAULT.stereoMode)); + Bundle colorInfoBundle = bundle.getBundle(FIELD_COLOR_INFO); if (colorInfoBundle != null) { builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle)); } // Audio specific. builder - .setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount)) - .setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate)) - .setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding)) - .setEncoderDelay(bundle.getInt(keyForField(FIELD_ENCODER_DELAY), DEFAULT.encoderDelay)) - .setEncoderPadding( - bundle.getInt(keyForField(FIELD_ENCODER_PADDING), DEFAULT.encoderPadding)) + .setChannelCount(bundle.getInt(FIELD_CHANNEL_COUNT, DEFAULT.channelCount)) + .setSampleRate(bundle.getInt(FIELD_SAMPLE_RATE, DEFAULT.sampleRate)) + .setPcmEncoding(bundle.getInt(FIELD_PCM_ENCODING, DEFAULT.pcmEncoding)) + .setEncoderDelay(bundle.getInt(FIELD_ENCODER_DELAY, DEFAULT.encoderDelay)) + .setEncoderPadding(bundle.getInt(FIELD_ENCODER_PADDING, DEFAULT.encoderPadding)) // Text specific. .setAccessibilityChannel( - bundle.getInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), DEFAULT.accessibilityChannel)) + bundle.getInt(FIELD_ACCESSIBILITY_CHANNEL, DEFAULT.accessibilityChannel)) // Source specific. - .setCryptoType(bundle.getInt(keyForField(FIELD_CRYPTO_TYPE), DEFAULT.cryptoType)); + .setCryptoType(bundle.getInt(FIELD_CRYPTO_TYPE, DEFAULT.cryptoType)); return builder.build(); } - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - private static String keyForInitializationData(int initialisationDataIndex) { - return keyForField(FIELD_INITIALIZATION_DATA) + return FIELD_INITIALIZATION_DATA + "_" + Integer.toString(initialisationDataIndex, Character.MAX_RADIX); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java b/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java index e14fd322e8..91ef693b97 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/HeartRating.java @@ -16,16 +16,11 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.Objects; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** * A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a @@ -80,21 +75,15 @@ public final class HeartRating extends Rating { private static final @RatingType int TYPE = RATING_TYPE_HEART; - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_HEART}) - private @interface FieldNumber {} - - private static final int FIELD_RATED = 1; - private static final int FIELD_IS_HEART = 2; + private static final String FIELD_RATED = Util.intToStringMaxRadix(1); + private static final String FIELD_IS_HEART = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); - bundle.putBoolean(keyForField(FIELD_RATED), rated); - bundle.putBoolean(keyForField(FIELD_IS_HEART), isHeart); + bundle.putInt(FIELD_RATING_TYPE, TYPE); + bundle.putBoolean(FIELD_RATED, rated); + bundle.putBoolean(FIELD_IS_HEART, isHeart); return bundle; } @@ -102,16 +91,10 @@ public final class HeartRating extends Rating { public static final Creator CREATOR = HeartRating::fromBundle; private static HeartRating fromBundle(Bundle bundle) { - checkArgument( - bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET) - == TYPE); - boolean isRated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false); + checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE); + boolean isRated = bundle.getBoolean(FIELD_RATED, /* defaultValue= */ false); return isRated - ? new HeartRating(bundle.getBoolean(keyForField(FIELD_IS_HEART), /* defaultValue= */ false)) + ? new HeartRating(bundle.getBoolean(FIELD_IS_HEART, /* defaultValue= */ false)) : new HeartRating(); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index 456149b8c7..615fb2b3ae 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -17,11 +17,9 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; -import static java.lang.annotation.ElementType.TYPE_USE; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.offline.StreamKey; @@ -31,10 +29,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.InlineMe; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1275,41 +1269,29 @@ public final class MediaItem implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TARGET_OFFSET_MS, - FIELD_MIN_OFFSET_MS, - FIELD_MAX_OFFSET_MS, - FIELD_MIN_PLAYBACK_SPEED, - FIELD_MAX_PLAYBACK_SPEED - }) - private @interface FieldNumber {} - - private static final int FIELD_TARGET_OFFSET_MS = 0; - private static final int FIELD_MIN_OFFSET_MS = 1; - private static final int FIELD_MAX_OFFSET_MS = 2; - private static final int FIELD_MIN_PLAYBACK_SPEED = 3; - private static final int FIELD_MAX_PLAYBACK_SPEED = 4; + private static final String FIELD_TARGET_OFFSET_MS = Util.intToStringMaxRadix(0); + private static final String FIELD_MIN_OFFSET_MS = Util.intToStringMaxRadix(1); + private static final String FIELD_MAX_OFFSET_MS = Util.intToStringMaxRadix(2); + private static final String FIELD_MIN_PLAYBACK_SPEED = Util.intToStringMaxRadix(3); + private static final String FIELD_MAX_PLAYBACK_SPEED = Util.intToStringMaxRadix(4); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); if (targetOffsetMs != UNSET.targetOffsetMs) { - bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs); + bundle.putLong(FIELD_TARGET_OFFSET_MS, targetOffsetMs); } if (minOffsetMs != UNSET.minOffsetMs) { - bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs); + bundle.putLong(FIELD_MIN_OFFSET_MS, minOffsetMs); } if (maxOffsetMs != UNSET.maxOffsetMs) { - bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs); + bundle.putLong(FIELD_MAX_OFFSET_MS, maxOffsetMs); } if (minPlaybackSpeed != UNSET.minPlaybackSpeed) { - bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed); + bundle.putFloat(FIELD_MIN_PLAYBACK_SPEED, minPlaybackSpeed); } if (maxPlaybackSpeed != UNSET.maxPlaybackSpeed) { - bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed); + bundle.putFloat(FIELD_MAX_PLAYBACK_SPEED, maxPlaybackSpeed); } return bundle; } @@ -1318,22 +1300,13 @@ public final class MediaItem implements Bundleable { public static final Creator CREATOR = bundle -> new LiveConfiguration( - bundle.getLong( - keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ UNSET.targetOffsetMs), - bundle.getLong( - keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ UNSET.minOffsetMs), - bundle.getLong( - keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ UNSET.maxOffsetMs), + bundle.getLong(FIELD_TARGET_OFFSET_MS, /* defaultValue= */ UNSET.targetOffsetMs), + bundle.getLong(FIELD_MIN_OFFSET_MS, /* defaultValue= */ UNSET.minOffsetMs), + bundle.getLong(FIELD_MAX_OFFSET_MS, /* defaultValue= */ UNSET.maxOffsetMs), bundle.getFloat( - keyForField(FIELD_MIN_PLAYBACK_SPEED), - /* defaultValue= */ UNSET.minPlaybackSpeed), + FIELD_MIN_PLAYBACK_SPEED, /* defaultValue= */ UNSET.minPlaybackSpeed), bundle.getFloat( - keyForField(FIELD_MAX_PLAYBACK_SPEED), - /* defaultValue= */ UNSET.maxPlaybackSpeed)); - - private static String keyForField(@LiveConfiguration.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } + FIELD_MAX_PLAYBACK_SPEED, /* defaultValue= */ UNSET.maxPlaybackSpeed)); } /** Properties for a text track. */ @@ -1720,42 +1693,29 @@ public final class MediaItem implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_START_POSITION_MS, - FIELD_END_POSITION_MS, - FIELD_RELATIVE_TO_LIVE_WINDOW, - FIELD_RELATIVE_TO_DEFAULT_POSITION, - FIELD_STARTS_AT_KEY_FRAME - }) - private @interface FieldNumber {} - - private static final int FIELD_START_POSITION_MS = 0; - private static final int FIELD_END_POSITION_MS = 1; - private static final int FIELD_RELATIVE_TO_LIVE_WINDOW = 2; - private static final int FIELD_RELATIVE_TO_DEFAULT_POSITION = 3; - private static final int FIELD_STARTS_AT_KEY_FRAME = 4; + private static final String FIELD_START_POSITION_MS = Util.intToStringMaxRadix(0); + private static final String FIELD_END_POSITION_MS = Util.intToStringMaxRadix(1); + private static final String FIELD_RELATIVE_TO_LIVE_WINDOW = Util.intToStringMaxRadix(2); + private static final String FIELD_RELATIVE_TO_DEFAULT_POSITION = Util.intToStringMaxRadix(3); + private static final String FIELD_STARTS_AT_KEY_FRAME = Util.intToStringMaxRadix(4); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); if (startPositionMs != UNSET.startPositionMs) { - bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs); + bundle.putLong(FIELD_START_POSITION_MS, startPositionMs); } if (endPositionMs != UNSET.endPositionMs) { - bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs); + bundle.putLong(FIELD_END_POSITION_MS, endPositionMs); } if (relativeToLiveWindow != UNSET.relativeToLiveWindow) { - bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow); + bundle.putBoolean(FIELD_RELATIVE_TO_LIVE_WINDOW, relativeToLiveWindow); } if (relativeToDefaultPosition != UNSET.relativeToDefaultPosition) { - bundle.putBoolean( - keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition); + bundle.putBoolean(FIELD_RELATIVE_TO_DEFAULT_POSITION, relativeToDefaultPosition); } if (startsAtKeyFrame != UNSET.startsAtKeyFrame) { - bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame); + bundle.putBoolean(FIELD_STARTS_AT_KEY_FRAME, startsAtKeyFrame); } return bundle; } @@ -1766,29 +1726,21 @@ public final class MediaItem implements Bundleable { new ClippingConfiguration.Builder() .setStartPositionMs( bundle.getLong( - keyForField(FIELD_START_POSITION_MS), - /* defaultValue= */ UNSET.startPositionMs)) + FIELD_START_POSITION_MS, /* defaultValue= */ UNSET.startPositionMs)) .setEndPositionMs( - bundle.getLong( - keyForField(FIELD_END_POSITION_MS), - /* defaultValue= */ UNSET.endPositionMs)) + bundle.getLong(FIELD_END_POSITION_MS, /* defaultValue= */ UNSET.endPositionMs)) .setRelativeToLiveWindow( bundle.getBoolean( - keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), + FIELD_RELATIVE_TO_LIVE_WINDOW, /* defaultValue= */ UNSET.relativeToLiveWindow)) .setRelativeToDefaultPosition( bundle.getBoolean( - keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), + FIELD_RELATIVE_TO_DEFAULT_POSITION, /* defaultValue= */ UNSET.relativeToDefaultPosition)) .setStartsAtKeyFrame( bundle.getBoolean( - keyForField(FIELD_STARTS_AT_KEY_FRAME), - /* defaultValue= */ UNSET.startsAtKeyFrame)) + FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame)) .buildClippingProperties(); - - private static String keyForField(@ClippingConfiguration.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -1906,27 +1858,21 @@ public final class MediaItem implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_MEDIA_URI, FIELD_SEARCH_QUERY, FIELD_EXTRAS}) - private @interface FieldNumber {} - - private static final int FIELD_MEDIA_URI = 0; - private static final int FIELD_SEARCH_QUERY = 1; - private static final int FIELD_EXTRAS = 2; + private static final String FIELD_MEDIA_URI = Util.intToStringMaxRadix(0); + private static final String FIELD_SEARCH_QUERY = Util.intToStringMaxRadix(1); + private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); if (mediaUri != null) { - bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri); + bundle.putParcelable(FIELD_MEDIA_URI, mediaUri); } if (searchQuery != null) { - bundle.putString(keyForField(FIELD_SEARCH_QUERY), searchQuery); + bundle.putString(FIELD_SEARCH_QUERY, searchQuery); } if (extras != null) { - bundle.putBundle(keyForField(FIELD_EXTRAS), extras); + bundle.putBundle(FIELD_EXTRAS, extras); } return bundle; } @@ -1935,14 +1881,10 @@ public final class MediaItem implements Bundleable { public static final Creator CREATOR = bundle -> new RequestMetadata.Builder() - .setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI))) - .setSearchQuery(bundle.getString(keyForField(FIELD_SEARCH_QUERY))) - .setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS))) + .setMediaUri(bundle.getParcelable(FIELD_MEDIA_URI)) + .setSearchQuery(bundle.getString(FIELD_SEARCH_QUERY)) + .setExtras(bundle.getBundle(FIELD_EXTRAS)) .build(); - - private static String keyForField(@RequestMetadata.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -2038,24 +1980,11 @@ public final class MediaItem implements Bundleable { } // Bundleable implementation. - - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_MEDIA_ID, - FIELD_LIVE_CONFIGURATION, - FIELD_MEDIA_METADATA, - FIELD_CLIPPING_PROPERTIES, - FIELD_REQUEST_METADATA - }) - private @interface FieldNumber {} - - private static final int FIELD_MEDIA_ID = 0; - private static final int FIELD_LIVE_CONFIGURATION = 1; - private static final int FIELD_MEDIA_METADATA = 2; - private static final int FIELD_CLIPPING_PROPERTIES = 3; - private static final int FIELD_REQUEST_METADATA = 4; + private static final String FIELD_MEDIA_ID = Util.intToStringMaxRadix(0); + private static final String FIELD_LIVE_CONFIGURATION = Util.intToStringMaxRadix(1); + private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2); + private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3); + private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4); /** * {@inheritDoc} @@ -2067,19 +1996,19 @@ public final class MediaItem implements Bundleable { public Bundle toBundle() { Bundle bundle = new Bundle(); if (!mediaId.equals(DEFAULT_MEDIA_ID)) { - bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId); + bundle.putString(FIELD_MEDIA_ID, mediaId); } if (!liveConfiguration.equals(LiveConfiguration.UNSET)) { - bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); + bundle.putBundle(FIELD_LIVE_CONFIGURATION, liveConfiguration.toBundle()); } if (!mediaMetadata.equals(MediaMetadata.EMPTY)) { - bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); + bundle.putBundle(FIELD_MEDIA_METADATA, mediaMetadata.toBundle()); } if (!clippingConfiguration.equals(ClippingConfiguration.UNSET)) { - bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle()); + bundle.putBundle(FIELD_CLIPPING_PROPERTIES, clippingConfiguration.toBundle()); } if (!requestMetadata.equals(RequestMetadata.EMPTY)) { - bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle()); + bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle()); } return bundle; } @@ -2093,31 +2022,29 @@ public final class MediaItem implements Bundleable { @SuppressWarnings("deprecation") // Unbundling to ClippingProperties while it still exists. private static MediaItem fromBundle(Bundle bundle) { - String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID), DEFAULT_MEDIA_ID)); - @Nullable - Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION)); + String mediaId = checkNotNull(bundle.getString(FIELD_MEDIA_ID, DEFAULT_MEDIA_ID)); + @Nullable Bundle liveConfigurationBundle = bundle.getBundle(FIELD_LIVE_CONFIGURATION); LiveConfiguration liveConfiguration; if (liveConfigurationBundle == null) { liveConfiguration = LiveConfiguration.UNSET; } else { liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle); } - @Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA)); + @Nullable Bundle mediaMetadataBundle = bundle.getBundle(FIELD_MEDIA_METADATA); MediaMetadata mediaMetadata; if (mediaMetadataBundle == null) { mediaMetadata = MediaMetadata.EMPTY; } else { mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle); } - @Nullable - Bundle clippingConfigurationBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES)); + @Nullable Bundle clippingConfigurationBundle = bundle.getBundle(FIELD_CLIPPING_PROPERTIES); ClippingProperties clippingConfiguration; if (clippingConfigurationBundle == null) { clippingConfiguration = ClippingProperties.UNSET; } else { clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle); } - @Nullable Bundle requestMetadataBundle = bundle.getBundle(keyForField(FIELD_REQUEST_METADATA)); + @Nullable Bundle requestMetadataBundle = bundle.getBundle(FIELD_REQUEST_METADATA); RequestMetadata requestMetadata; if (requestMetadataBundle == null) { requestMetadata = RequestMetadata.EMPTY; @@ -2132,8 +2059,4 @@ public final class MediaItem implements Bundleable { mediaMetadata, requestMetadata); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index 72cd709204..4b6900e8fd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -1095,183 +1095,142 @@ public final class MediaMetadata implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TITLE, - FIELD_ARTIST, - FIELD_ALBUM_TITLE, - FIELD_ALBUM_ARTIST, - FIELD_DISPLAY_TITLE, - FIELD_SUBTITLE, - FIELD_DESCRIPTION, - FIELD_MEDIA_URI, - FIELD_USER_RATING, - FIELD_OVERALL_RATING, - FIELD_ARTWORK_DATA, - FIELD_ARTWORK_DATA_TYPE, - FIELD_ARTWORK_URI, - FIELD_TRACK_NUMBER, - FIELD_TOTAL_TRACK_COUNT, - FIELD_FOLDER_TYPE, - FIELD_IS_PLAYABLE, - FIELD_RECORDING_YEAR, - FIELD_RECORDING_MONTH, - FIELD_RECORDING_DAY, - FIELD_RELEASE_YEAR, - FIELD_RELEASE_MONTH, - FIELD_RELEASE_DAY, - FIELD_WRITER, - FIELD_COMPOSER, - FIELD_CONDUCTOR, - FIELD_DISC_NUMBER, - FIELD_TOTAL_DISC_COUNT, - FIELD_GENRE, - FIELD_COMPILATION, - FIELD_STATION, - FIELD_MEDIA_TYPE, - FIELD_IS_BROWSABLE, - FIELD_EXTRAS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TITLE = 0; - private static final int FIELD_ARTIST = 1; - private static final int FIELD_ALBUM_TITLE = 2; - private static final int FIELD_ALBUM_ARTIST = 3; - private static final int FIELD_DISPLAY_TITLE = 4; - private static final int FIELD_SUBTITLE = 5; - private static final int FIELD_DESCRIPTION = 6; - private static final int FIELD_MEDIA_URI = 7; - private static final int FIELD_USER_RATING = 8; - private static final int FIELD_OVERALL_RATING = 9; - private static final int FIELD_ARTWORK_DATA = 10; - private static final int FIELD_ARTWORK_URI = 11; - private static final int FIELD_TRACK_NUMBER = 12; - private static final int FIELD_TOTAL_TRACK_COUNT = 13; - private static final int FIELD_FOLDER_TYPE = 14; - private static final int FIELD_IS_PLAYABLE = 15; - private static final int FIELD_RECORDING_YEAR = 16; - private static final int FIELD_RECORDING_MONTH = 17; - private static final int FIELD_RECORDING_DAY = 18; - private static final int FIELD_RELEASE_YEAR = 19; - private static final int FIELD_RELEASE_MONTH = 20; - private static final int FIELD_RELEASE_DAY = 21; - private static final int FIELD_WRITER = 22; - private static final int FIELD_COMPOSER = 23; - private static final int FIELD_CONDUCTOR = 24; - private static final int FIELD_DISC_NUMBER = 25; - private static final int FIELD_TOTAL_DISC_COUNT = 26; - private static final int FIELD_GENRE = 27; - private static final int FIELD_COMPILATION = 28; - private static final int FIELD_ARTWORK_DATA_TYPE = 29; - private static final int FIELD_STATION = 30; - private static final int FIELD_MEDIA_TYPE = 31; - private static final int FIELD_IS_BROWSABLE = 32; - private static final int FIELD_EXTRAS = 1000; + private static final String FIELD_TITLE = Util.intToStringMaxRadix(0); + private static final String FIELD_ARTIST = Util.intToStringMaxRadix(1); + private static final String FIELD_ALBUM_TITLE = Util.intToStringMaxRadix(2); + private static final String FIELD_ALBUM_ARTIST = Util.intToStringMaxRadix(3); + private static final String FIELD_DISPLAY_TITLE = Util.intToStringMaxRadix(4); + private static final String FIELD_SUBTITLE = Util.intToStringMaxRadix(5); + private static final String FIELD_DESCRIPTION = Util.intToStringMaxRadix(6); + // 7 is reserved to maintain backward compatibility for a previously defined field. + private static final String FIELD_USER_RATING = Util.intToStringMaxRadix(8); + private static final String FIELD_OVERALL_RATING = Util.intToStringMaxRadix(9); + private static final String FIELD_ARTWORK_DATA = Util.intToStringMaxRadix(10); + private static final String FIELD_ARTWORK_URI = Util.intToStringMaxRadix(11); + private static final String FIELD_TRACK_NUMBER = Util.intToStringMaxRadix(12); + private static final String FIELD_TOTAL_TRACK_COUNT = Util.intToStringMaxRadix(13); + private static final String FIELD_FOLDER_TYPE = Util.intToStringMaxRadix(14); + private static final String FIELD_IS_PLAYABLE = Util.intToStringMaxRadix(15); + private static final String FIELD_RECORDING_YEAR = Util.intToStringMaxRadix(16); + private static final String FIELD_RECORDING_MONTH = Util.intToStringMaxRadix(17); + private static final String FIELD_RECORDING_DAY = Util.intToStringMaxRadix(18); + private static final String FIELD_RELEASE_YEAR = Util.intToStringMaxRadix(19); + private static final String FIELD_RELEASE_MONTH = Util.intToStringMaxRadix(20); + private static final String FIELD_RELEASE_DAY = Util.intToStringMaxRadix(21); + private static final String FIELD_WRITER = Util.intToStringMaxRadix(22); + private static final String FIELD_COMPOSER = Util.intToStringMaxRadix(23); + private static final String FIELD_CONDUCTOR = Util.intToStringMaxRadix(24); + private static final String FIELD_DISC_NUMBER = Util.intToStringMaxRadix(25); + private static final String FIELD_TOTAL_DISC_COUNT = Util.intToStringMaxRadix(26); + private static final String FIELD_GENRE = Util.intToStringMaxRadix(27); + private static final String FIELD_COMPILATION = Util.intToStringMaxRadix(28); + private static final String FIELD_ARTWORK_DATA_TYPE = Util.intToStringMaxRadix(29); + private static final String FIELD_STATION = Util.intToStringMaxRadix(30); + private static final String FIELD_MEDIA_TYPE = Util.intToStringMaxRadix(31); + private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32); + private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); if (title != null) { - bundle.putCharSequence(keyForField(FIELD_TITLE), title); + bundle.putCharSequence(FIELD_TITLE, title); } if (artist != null) { - bundle.putCharSequence(keyForField(FIELD_ARTIST), artist); + bundle.putCharSequence(FIELD_ARTIST, artist); } if (albumTitle != null) { - bundle.putCharSequence(keyForField(FIELD_ALBUM_TITLE), albumTitle); + bundle.putCharSequence(FIELD_ALBUM_TITLE, albumTitle); } if (albumArtist != null) { - bundle.putCharSequence(keyForField(FIELD_ALBUM_ARTIST), albumArtist); + bundle.putCharSequence(FIELD_ALBUM_ARTIST, albumArtist); } if (displayTitle != null) { - bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); + bundle.putCharSequence(FIELD_DISPLAY_TITLE, displayTitle); } if (subtitle != null) { - bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); + bundle.putCharSequence(FIELD_SUBTITLE, subtitle); } if (description != null) { - bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); + bundle.putCharSequence(FIELD_DESCRIPTION, description); } if (artworkData != null) { - bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); + bundle.putByteArray(FIELD_ARTWORK_DATA, artworkData); } if (artworkUri != null) { - bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); + bundle.putParcelable(FIELD_ARTWORK_URI, artworkUri); } if (writer != null) { - bundle.putCharSequence(keyForField(FIELD_WRITER), writer); + bundle.putCharSequence(FIELD_WRITER, writer); } if (composer != null) { - bundle.putCharSequence(keyForField(FIELD_COMPOSER), composer); + bundle.putCharSequence(FIELD_COMPOSER, composer); } if (conductor != null) { - bundle.putCharSequence(keyForField(FIELD_CONDUCTOR), conductor); + bundle.putCharSequence(FIELD_CONDUCTOR, conductor); } if (genre != null) { - bundle.putCharSequence(keyForField(FIELD_GENRE), genre); + bundle.putCharSequence(FIELD_GENRE, genre); } if (compilation != null) { - bundle.putCharSequence(keyForField(FIELD_COMPILATION), compilation); + bundle.putCharSequence(FIELD_COMPILATION, compilation); } if (station != null) { - bundle.putCharSequence(keyForField(FIELD_STATION), station); + bundle.putCharSequence(FIELD_STATION, station); } if (userRating != null) { - bundle.putBundle(keyForField(FIELD_USER_RATING), userRating.toBundle()); + bundle.putBundle(FIELD_USER_RATING, userRating.toBundle()); } if (overallRating != null) { - bundle.putBundle(keyForField(FIELD_OVERALL_RATING), overallRating.toBundle()); + bundle.putBundle(FIELD_OVERALL_RATING, overallRating.toBundle()); } if (trackNumber != null) { - bundle.putInt(keyForField(FIELD_TRACK_NUMBER), trackNumber); + bundle.putInt(FIELD_TRACK_NUMBER, trackNumber); } if (totalTrackCount != null) { - bundle.putInt(keyForField(FIELD_TOTAL_TRACK_COUNT), totalTrackCount); + bundle.putInt(FIELD_TOTAL_TRACK_COUNT, totalTrackCount); } if (folderType != null) { - bundle.putInt(keyForField(FIELD_FOLDER_TYPE), folderType); + bundle.putInt(FIELD_FOLDER_TYPE, folderType); } if (isBrowsable != null) { - bundle.putBoolean(keyForField(FIELD_IS_BROWSABLE), isBrowsable); + bundle.putBoolean(FIELD_IS_BROWSABLE, isBrowsable); } if (isPlayable != null) { - bundle.putBoolean(keyForField(FIELD_IS_PLAYABLE), isPlayable); + bundle.putBoolean(FIELD_IS_PLAYABLE, isPlayable); } if (recordingYear != null) { - bundle.putInt(keyForField(FIELD_RECORDING_YEAR), recordingYear); + bundle.putInt(FIELD_RECORDING_YEAR, recordingYear); } if (recordingMonth != null) { - bundle.putInt(keyForField(FIELD_RECORDING_MONTH), recordingMonth); + bundle.putInt(FIELD_RECORDING_MONTH, recordingMonth); } if (recordingDay != null) { - bundle.putInt(keyForField(FIELD_RECORDING_DAY), recordingDay); + bundle.putInt(FIELD_RECORDING_DAY, recordingDay); } if (releaseYear != null) { - bundle.putInt(keyForField(FIELD_RELEASE_YEAR), releaseYear); + bundle.putInt(FIELD_RELEASE_YEAR, releaseYear); } if (releaseMonth != null) { - bundle.putInt(keyForField(FIELD_RELEASE_MONTH), releaseMonth); + bundle.putInt(FIELD_RELEASE_MONTH, releaseMonth); } if (releaseDay != null) { - bundle.putInt(keyForField(FIELD_RELEASE_DAY), releaseDay); + bundle.putInt(FIELD_RELEASE_DAY, releaseDay); } if (discNumber != null) { - bundle.putInt(keyForField(FIELD_DISC_NUMBER), discNumber); + bundle.putInt(FIELD_DISC_NUMBER, discNumber); } if (totalDiscCount != null) { - bundle.putInt(keyForField(FIELD_TOTAL_DISC_COUNT), totalDiscCount); + bundle.putInt(FIELD_TOTAL_DISC_COUNT, totalDiscCount); } if (artworkDataType != null) { - bundle.putInt(keyForField(FIELD_ARTWORK_DATA_TYPE), artworkDataType); + bundle.putInt(FIELD_ARTWORK_DATA_TYPE, artworkDataType); } if (mediaType != null) { - bundle.putInt(keyForField(FIELD_MEDIA_TYPE), mediaType); + bundle.putInt(FIELD_MEDIA_TYPE, mediaType); } if (extras != null) { - bundle.putBundle(keyForField(FIELD_EXTRAS), extras); + bundle.putBundle(FIELD_EXTRAS, extras); } return bundle; } @@ -1282,89 +1241,85 @@ public final class MediaMetadata implements Bundleable { private static MediaMetadata fromBundle(Bundle bundle) { Builder builder = new Builder(); builder - .setTitle(bundle.getCharSequence(keyForField(FIELD_TITLE))) - .setArtist(bundle.getCharSequence(keyForField(FIELD_ARTIST))) - .setAlbumTitle(bundle.getCharSequence(keyForField(FIELD_ALBUM_TITLE))) - .setAlbumArtist(bundle.getCharSequence(keyForField(FIELD_ALBUM_ARTIST))) - .setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE))) - .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) - .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) + .setTitle(bundle.getCharSequence(FIELD_TITLE)) + .setArtist(bundle.getCharSequence(FIELD_ARTIST)) + .setAlbumTitle(bundle.getCharSequence(FIELD_ALBUM_TITLE)) + .setAlbumArtist(bundle.getCharSequence(FIELD_ALBUM_ARTIST)) + .setDisplayTitle(bundle.getCharSequence(FIELD_DISPLAY_TITLE)) + .setSubtitle(bundle.getCharSequence(FIELD_SUBTITLE)) + .setDescription(bundle.getCharSequence(FIELD_DESCRIPTION)) .setArtworkData( - bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)), - bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE)) - ? bundle.getInt(keyForField(FIELD_ARTWORK_DATA_TYPE)) + bundle.getByteArray(FIELD_ARTWORK_DATA), + bundle.containsKey(FIELD_ARTWORK_DATA_TYPE) + ? bundle.getInt(FIELD_ARTWORK_DATA_TYPE) : null) - .setArtworkUri(bundle.getParcelable(keyForField(FIELD_ARTWORK_URI))) - .setWriter(bundle.getCharSequence(keyForField(FIELD_WRITER))) - .setComposer(bundle.getCharSequence(keyForField(FIELD_COMPOSER))) - .setConductor(bundle.getCharSequence(keyForField(FIELD_CONDUCTOR))) - .setGenre(bundle.getCharSequence(keyForField(FIELD_GENRE))) - .setCompilation(bundle.getCharSequence(keyForField(FIELD_COMPILATION))) - .setStation(bundle.getCharSequence(keyForField(FIELD_STATION))) - .setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS))); + .setArtworkUri(bundle.getParcelable(FIELD_ARTWORK_URI)) + .setWriter(bundle.getCharSequence(FIELD_WRITER)) + .setComposer(bundle.getCharSequence(FIELD_COMPOSER)) + .setConductor(bundle.getCharSequence(FIELD_CONDUCTOR)) + .setGenre(bundle.getCharSequence(FIELD_GENRE)) + .setCompilation(bundle.getCharSequence(FIELD_COMPILATION)) + .setStation(bundle.getCharSequence(FIELD_STATION)) + .setExtras(bundle.getBundle(FIELD_EXTRAS)); - if (bundle.containsKey(keyForField(FIELD_USER_RATING))) { - @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_USER_RATING)); + if (bundle.containsKey(FIELD_USER_RATING)) { + @Nullable Bundle fieldBundle = bundle.getBundle(FIELD_USER_RATING); if (fieldBundle != null) { builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle)); } } - if (bundle.containsKey(keyForField(FIELD_OVERALL_RATING))) { - @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_OVERALL_RATING)); + if (bundle.containsKey(FIELD_OVERALL_RATING)) { + @Nullable Bundle fieldBundle = bundle.getBundle(FIELD_OVERALL_RATING); if (fieldBundle != null) { builder.setOverallRating(Rating.CREATOR.fromBundle(fieldBundle)); } } - if (bundle.containsKey(keyForField(FIELD_TRACK_NUMBER))) { - builder.setTrackNumber(bundle.getInt(keyForField(FIELD_TRACK_NUMBER))); + if (bundle.containsKey(FIELD_TRACK_NUMBER)) { + builder.setTrackNumber(bundle.getInt(FIELD_TRACK_NUMBER)); } - if (bundle.containsKey(keyForField(FIELD_TOTAL_TRACK_COUNT))) { - builder.setTotalTrackCount(bundle.getInt(keyForField(FIELD_TOTAL_TRACK_COUNT))); + if (bundle.containsKey(FIELD_TOTAL_TRACK_COUNT)) { + builder.setTotalTrackCount(bundle.getInt(FIELD_TOTAL_TRACK_COUNT)); } - if (bundle.containsKey(keyForField(FIELD_FOLDER_TYPE))) { - builder.setFolderType(bundle.getInt(keyForField(FIELD_FOLDER_TYPE))); + if (bundle.containsKey(FIELD_FOLDER_TYPE)) { + builder.setFolderType(bundle.getInt(FIELD_FOLDER_TYPE)); } - if (bundle.containsKey(keyForField(FIELD_IS_BROWSABLE))) { - builder.setIsBrowsable(bundle.getBoolean(keyForField(FIELD_IS_BROWSABLE))); + if (bundle.containsKey(FIELD_IS_BROWSABLE)) { + builder.setIsBrowsable(bundle.getBoolean(FIELD_IS_BROWSABLE)); } - if (bundle.containsKey(keyForField(FIELD_IS_PLAYABLE))) { - builder.setIsPlayable(bundle.getBoolean(keyForField(FIELD_IS_PLAYABLE))); + if (bundle.containsKey(FIELD_IS_PLAYABLE)) { + builder.setIsPlayable(bundle.getBoolean(FIELD_IS_PLAYABLE)); } - if (bundle.containsKey(keyForField(FIELD_RECORDING_YEAR))) { - builder.setRecordingYear(bundle.getInt(keyForField(FIELD_RECORDING_YEAR))); + if (bundle.containsKey(FIELD_RECORDING_YEAR)) { + builder.setRecordingYear(bundle.getInt(FIELD_RECORDING_YEAR)); } - if (bundle.containsKey(keyForField(FIELD_RECORDING_MONTH))) { - builder.setRecordingMonth(bundle.getInt(keyForField(FIELD_RECORDING_MONTH))); + if (bundle.containsKey(FIELD_RECORDING_MONTH)) { + builder.setRecordingMonth(bundle.getInt(FIELD_RECORDING_MONTH)); } - if (bundle.containsKey(keyForField(FIELD_RECORDING_DAY))) { - builder.setRecordingDay(bundle.getInt(keyForField(FIELD_RECORDING_DAY))); + if (bundle.containsKey(FIELD_RECORDING_DAY)) { + builder.setRecordingDay(bundle.getInt(FIELD_RECORDING_DAY)); } - if (bundle.containsKey(keyForField(FIELD_RELEASE_YEAR))) { - builder.setReleaseYear(bundle.getInt(keyForField(FIELD_RELEASE_YEAR))); + if (bundle.containsKey(FIELD_RELEASE_YEAR)) { + builder.setReleaseYear(bundle.getInt(FIELD_RELEASE_YEAR)); } - if (bundle.containsKey(keyForField(FIELD_RELEASE_MONTH))) { - builder.setReleaseMonth(bundle.getInt(keyForField(FIELD_RELEASE_MONTH))); + if (bundle.containsKey(FIELD_RELEASE_MONTH)) { + builder.setReleaseMonth(bundle.getInt(FIELD_RELEASE_MONTH)); } - if (bundle.containsKey(keyForField(FIELD_RELEASE_DAY))) { - builder.setReleaseDay(bundle.getInt(keyForField(FIELD_RELEASE_DAY))); + if (bundle.containsKey(FIELD_RELEASE_DAY)) { + builder.setReleaseDay(bundle.getInt(FIELD_RELEASE_DAY)); } - if (bundle.containsKey(keyForField(FIELD_DISC_NUMBER))) { - builder.setDiscNumber(bundle.getInt(keyForField(FIELD_DISC_NUMBER))); + if (bundle.containsKey(FIELD_DISC_NUMBER)) { + builder.setDiscNumber(bundle.getInt(FIELD_DISC_NUMBER)); } - if (bundle.containsKey(keyForField(FIELD_TOTAL_DISC_COUNT))) { - builder.setTotalDiscCount(bundle.getInt(keyForField(FIELD_TOTAL_DISC_COUNT))); + if (bundle.containsKey(FIELD_TOTAL_DISC_COUNT)) { + builder.setTotalDiscCount(bundle.getInt(FIELD_TOTAL_DISC_COUNT)); } - if (bundle.containsKey(keyForField(FIELD_MEDIA_TYPE))) { - builder.setMediaType(bundle.getInt(keyForField(FIELD_MEDIA_TYPE))); + if (bundle.containsKey(FIELD_MEDIA_TYPE)) { + builder.setMediaType(bundle.getInt(FIELD_MEDIA_TYPE)); } return builder.build(); } - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) { switch (mediaType) { case MEDIA_TYPE_ALBUM: diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java b/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java index 53521b53a2..3d30d493a2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PercentageRating.java @@ -16,17 +16,12 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.FloatRange; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.Objects; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** A rating expressed as a percentage. */ public final class PercentageRating extends Rating { @@ -78,19 +73,13 @@ public final class PercentageRating extends Rating { private static final @RatingType int TYPE = RATING_TYPE_PERCENTAGE; - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_RATING_TYPE, FIELD_PERCENT}) - private @interface FieldNumber {} - - private static final int FIELD_PERCENT = 1; + private static final String FIELD_PERCENT = Util.intToStringMaxRadix(1); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); - bundle.putFloat(keyForField(FIELD_PERCENT), percent); + bundle.putInt(FIELD_RATING_TYPE, TYPE); + bundle.putFloat(FIELD_PERCENT, percent); return bundle; } @@ -98,14 +87,8 @@ public final class PercentageRating extends Rating { public static final Creator CREATOR = PercentageRating::fromBundle; private static PercentageRating fromBundle(Bundle bundle) { - checkArgument( - bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET) - == TYPE); - float percent = bundle.getFloat(keyForField(FIELD_PERCENT), /* defaultValue= */ RATING_UNSET); + checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE); + float percent = bundle.getFloat(FIELD_PERCENT, /* defaultValue= */ RATING_UNSET); return percent == RATING_UNSET ? new PercentageRating() : new PercentageRating(percent); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index d3f23a0262..b2950639e0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -343,13 +343,12 @@ public class PlaybackException extends Exception implements Bundleable { /** Creates a new instance using the fields obtained from the given {@link Bundle}. */ protected PlaybackException(Bundle bundle) { this( - /* message= */ bundle.getString(keyForField(FIELD_STRING_MESSAGE)), + /* message= */ bundle.getString(FIELD_STRING_MESSAGE), /* cause= */ getCauseFromBundle(bundle), /* errorCode= */ bundle.getInt( - keyForField(FIELD_INT_ERROR_CODE), /* defaultValue= */ ERROR_CODE_UNSPECIFIED), + FIELD_INT_ERROR_CODE, /* defaultValue= */ ERROR_CODE_UNSPECIFIED), /* timestampMs= */ bundle.getLong( - keyForField(FIELD_LONG_TIMESTAMP_MS), - /* defaultValue= */ SystemClock.elapsedRealtime())); + FIELD_LONG_TIMESTAMP_MS, /* defaultValue= */ SystemClock.elapsedRealtime())); } /** Creates a new instance using the given values. */ @@ -397,18 +396,18 @@ public class PlaybackException extends Exception implements Bundleable { // Bundleable implementation. - private static final int FIELD_INT_ERROR_CODE = 0; - private static final int FIELD_LONG_TIMESTAMP_MS = 1; - private static final int FIELD_STRING_MESSAGE = 2; - private static final int FIELD_STRING_CAUSE_CLASS_NAME = 3; - private static final int FIELD_STRING_CAUSE_MESSAGE = 4; + private static final String FIELD_INT_ERROR_CODE = Util.intToStringMaxRadix(0); + private static final String FIELD_LONG_TIMESTAMP_MS = Util.intToStringMaxRadix(1); + private static final String FIELD_STRING_MESSAGE = Util.intToStringMaxRadix(2); + private static final String FIELD_STRING_CAUSE_CLASS_NAME = Util.intToStringMaxRadix(3); + private static final String FIELD_STRING_CAUSE_MESSAGE = Util.intToStringMaxRadix(4); /** * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()} * and {@link Bundleable.Creator}. * *

    Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative - * offset on this constant and passing the result to {@link #keyForField(int)}. + * offset on this constant and passing the result to {@link Util#intToStringMaxRadix(int)}. */ protected static final int FIELD_CUSTOM_ID_BASE = 1000; @@ -419,28 +418,17 @@ public class PlaybackException extends Exception implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_INT_ERROR_CODE), errorCode); - bundle.putLong(keyForField(FIELD_LONG_TIMESTAMP_MS), timestampMs); - bundle.putString(keyForField(FIELD_STRING_MESSAGE), getMessage()); + bundle.putInt(FIELD_INT_ERROR_CODE, errorCode); + bundle.putLong(FIELD_LONG_TIMESTAMP_MS, timestampMs); + bundle.putString(FIELD_STRING_MESSAGE, getMessage()); @Nullable Throwable cause = getCause(); if (cause != null) { - bundle.putString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME), cause.getClass().getName()); - bundle.putString(keyForField(FIELD_STRING_CAUSE_MESSAGE), cause.getMessage()); + bundle.putString(FIELD_STRING_CAUSE_CLASS_NAME, cause.getClass().getName()); + bundle.putString(FIELD_STRING_CAUSE_MESSAGE, cause.getMessage()); } return bundle; } - /** - * Converts the given field number to a string which can be used as a field key when implementing - * {@link #toBundle()} and {@link Bundleable.Creator}. - * - *

    Subclasses should use {@code field} values greater than or equal to {@link - * #FIELD_CUSTOM_ID_BASE}. - */ - protected static String keyForField(int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - // Creates a new {@link Throwable} with possibly {@code null} message. @SuppressWarnings("nullness:argument") private static Throwable createThrowable(Class clazz, @Nullable String message) @@ -456,8 +444,8 @@ public class PlaybackException extends Exception implements Bundleable { @Nullable private static Throwable getCauseFromBundle(Bundle bundle) { - @Nullable String causeClassName = bundle.getString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME)); - @Nullable String causeMessage = bundle.getString(keyForField(FIELD_STRING_CAUSE_MESSAGE)); + @Nullable String causeClassName = bundle.getString(FIELD_STRING_CAUSE_CLASS_NAME); + @Nullable String causeMessage = bundle.getString(FIELD_STRING_CAUSE_MESSAGE); @Nullable Throwable cause = null; if (!TextUtils.isEmpty(causeClassName)) { try { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java index 5edb9af6b4..185dfc0190 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java @@ -15,19 +15,12 @@ */ package com.google.android.exoplayer2; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; import androidx.annotation.CheckResult; import androidx.annotation.FloatRange; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; 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.annotation.Target; /** Parameters that apply to playback, including speed setting. */ public final class PlaybackParameters implements Bundleable { @@ -120,32 +113,22 @@ public final class PlaybackParameters implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_SPEED, FIELD_PITCH}) - private @interface FieldNumber {} - - private static final int FIELD_SPEED = 0; - private static final int FIELD_PITCH = 1; + private static final String FIELD_SPEED = Util.intToStringMaxRadix(0); + private static final String FIELD_PITCH = Util.intToStringMaxRadix(1); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putFloat(keyForField(FIELD_SPEED), speed); - bundle.putFloat(keyForField(FIELD_PITCH), pitch); + bundle.putFloat(FIELD_SPEED, speed); + bundle.putFloat(FIELD_PITCH, pitch); return bundle; } /** Object that can restore {@link PlaybackParameters} from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> { - float speed = bundle.getFloat(keyForField(FIELD_SPEED), /* defaultValue= */ 1f); - float pitch = bundle.getFloat(keyForField(FIELD_PITCH), /* defaultValue= */ 1f); + float speed = bundle.getFloat(FIELD_SPEED, /* defaultValue= */ 1f); + float pitch = bundle.getFloat(FIELD_PITCH, /* defaultValue= */ 1f); return new PlaybackParameters(speed, pitch); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 3887835b4e..02b552769e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -269,27 +269,14 @@ public interface Player { } // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_MEDIA_ITEM_INDEX, - FIELD_MEDIA_ITEM, - FIELD_PERIOD_INDEX, - FIELD_POSITION_MS, - FIELD_CONTENT_POSITION_MS, - FIELD_AD_GROUP_INDEX, - FIELD_AD_INDEX_IN_AD_GROUP - }) - private @interface FieldNumber {} - private static final int FIELD_MEDIA_ITEM_INDEX = 0; - private static final int FIELD_MEDIA_ITEM = 1; - private static final int FIELD_PERIOD_INDEX = 2; - private static final int FIELD_POSITION_MS = 3; - private static final int FIELD_CONTENT_POSITION_MS = 4; - private static final int FIELD_AD_GROUP_INDEX = 5; - private static final int FIELD_AD_INDEX_IN_AD_GROUP = 6; + private static final String FIELD_MEDIA_ITEM_INDEX = Util.intToStringMaxRadix(0); + private static final String FIELD_MEDIA_ITEM = Util.intToStringMaxRadix(1); + private static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(2); + private static final String FIELD_POSITION_MS = Util.intToStringMaxRadix(3); + private static final String FIELD_CONTENT_POSITION_MS = Util.intToStringMaxRadix(4); + private static final String FIELD_AD_GROUP_INDEX = Util.intToStringMaxRadix(5); + private static final String FIELD_AD_INDEX_IN_AD_GROUP = Util.intToStringMaxRadix(6); /** * {@inheritDoc} @@ -300,15 +287,15 @@ public interface Player { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex); + bundle.putInt(FIELD_MEDIA_ITEM_INDEX, mediaItemIndex); if (mediaItem != null) { - bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle()); + bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); } - bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex); - bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs); - bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs); - bundle.putInt(keyForField(FIELD_AD_GROUP_INDEX), adGroupIndex); - bundle.putInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), adIndexInAdGroup); + bundle.putInt(FIELD_PERIOD_INDEX, periodIndex); + bundle.putLong(FIELD_POSITION_MS, positionMs); + bundle.putLong(FIELD_CONTENT_POSITION_MS, contentPositionMs); + bundle.putInt(FIELD_AD_GROUP_INDEX, adGroupIndex); + bundle.putInt(FIELD_AD_INDEX_IN_AD_GROUP, adIndexInAdGroup); return bundle; } @@ -316,22 +303,18 @@ public interface Player { public static final Creator CREATOR = PositionInfo::fromBundle; private static PositionInfo fromBundle(Bundle bundle) { - int mediaItemIndex = - bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET); - @Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)); + int mediaItemIndex = bundle.getInt(FIELD_MEDIA_ITEM_INDEX, /* defaultValue= */ C.INDEX_UNSET); + @Nullable Bundle mediaItemBundle = bundle.getBundle(FIELD_MEDIA_ITEM); @Nullable MediaItem mediaItem = mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle); - int periodIndex = - bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET); - long positionMs = - bundle.getLong(keyForField(FIELD_POSITION_MS), /* defaultValue= */ C.TIME_UNSET); + int periodIndex = bundle.getInt(FIELD_PERIOD_INDEX, /* defaultValue= */ C.INDEX_UNSET); + long positionMs = bundle.getLong(FIELD_POSITION_MS, /* defaultValue= */ C.TIME_UNSET); long contentPositionMs = - bundle.getLong(keyForField(FIELD_CONTENT_POSITION_MS), /* defaultValue= */ C.TIME_UNSET); - int adGroupIndex = - bundle.getInt(keyForField(FIELD_AD_GROUP_INDEX), /* defaultValue= */ C.INDEX_UNSET); + bundle.getLong(FIELD_CONTENT_POSITION_MS, /* defaultValue= */ C.TIME_UNSET); + int adGroupIndex = bundle.getInt(FIELD_AD_GROUP_INDEX, /* defaultValue= */ C.INDEX_UNSET); int adIndexInAdGroup = - bundle.getInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), /* defaultValue= */ C.INDEX_UNSET); + bundle.getInt(FIELD_AD_INDEX_IN_AD_GROUP, /* defaultValue= */ C.INDEX_UNSET); return new PositionInfo( /* windowUid= */ null, mediaItemIndex, @@ -343,10 +326,6 @@ public interface Player { adGroupIndex, adIndexInAdGroup); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -579,13 +558,7 @@ public interface Player { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_COMMANDS}) - private @interface FieldNumber {} - - private static final int FIELD_COMMANDS = 0; + private static final String FIELD_COMMANDS = Util.intToStringMaxRadix(0); @Override public Bundle toBundle() { @@ -594,7 +567,7 @@ public interface Player { for (int i = 0; i < flags.size(); i++) { commandsBundle.add(flags.get(i)); } - bundle.putIntegerArrayList(keyForField(FIELD_COMMANDS), commandsBundle); + bundle.putIntegerArrayList(FIELD_COMMANDS, commandsBundle); return bundle; } @@ -602,8 +575,7 @@ public interface Player { public static final Creator CREATOR = Commands::fromBundle; private static Commands fromBundle(Bundle bundle) { - @Nullable - ArrayList commands = bundle.getIntegerArrayList(keyForField(FIELD_COMMANDS)); + @Nullable ArrayList commands = bundle.getIntegerArrayList(FIELD_COMMANDS); if (commands == null) { return Commands.EMPTY; } @@ -613,10 +585,6 @@ public interface Player { } return builder.build(); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Rating.java b/library/common/src/main/java/com/google/android/exoplayer2/Rating.java index 4fdf140ac7..1962db64c0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Rating.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Rating.java @@ -19,6 +19,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.IntDef; +import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -59,21 +60,14 @@ public abstract class Rating implements Bundleable { /* package */ static final int RATING_TYPE_STAR = 2; /* package */ static final int RATING_TYPE_THUMB = 3; - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_RATING_TYPE}) - private @interface FieldNumber {} - - /* package */ static final int FIELD_RATING_TYPE = 0; + /* package */ static final String FIELD_RATING_TYPE = Util.intToStringMaxRadix(0); /** Object that can restore a {@link Rating} from a {@link Bundle}. */ public static final Creator CREATOR = Rating::fromBundle; private static Rating fromBundle(Bundle bundle) { @RatingType - int ratingType = - bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET); + int ratingType = bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET); switch (ratingType) { case RATING_TYPE_HEART: return HeartRating.CREATOR.fromBundle(bundle); @@ -88,8 +82,4 @@ public abstract class Rating implements Bundleable { throw new IllegalArgumentException("Unknown RatingType: " + ratingType); } } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java b/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java index d82055a7ac..8233fed2e9 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/StarRating.java @@ -16,18 +16,13 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.FloatRange; -import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.Objects; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** A rating expressed as a fractional number of stars. */ public final class StarRating extends Rating { @@ -105,21 +100,15 @@ public final class StarRating extends Rating { private static final @RatingType int TYPE = RATING_TYPE_STAR; private static final int MAX_STARS_DEFAULT = 5; - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_RATING_TYPE, FIELD_MAX_STARS, FIELD_STAR_RATING}) - private @interface FieldNumber {} - - private static final int FIELD_MAX_STARS = 1; - private static final int FIELD_STAR_RATING = 2; + private static final String FIELD_MAX_STARS = Util.intToStringMaxRadix(1); + private static final String FIELD_STAR_RATING = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); - bundle.putInt(keyForField(FIELD_MAX_STARS), maxStars); - bundle.putFloat(keyForField(FIELD_STAR_RATING), starRating); + bundle.putInt(FIELD_RATING_TYPE, TYPE); + bundle.putInt(FIELD_MAX_STARS, maxStars); + bundle.putFloat(FIELD_STAR_RATING, starRating); return bundle; } @@ -127,19 +116,11 @@ public final class StarRating extends Rating { public static final Creator CREATOR = StarRating::fromBundle; private static StarRating fromBundle(Bundle bundle) { - checkArgument( - bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET) - == TYPE); - int maxStars = - bundle.getInt(keyForField(FIELD_MAX_STARS), /* defaultValue= */ MAX_STARS_DEFAULT); - float starRating = - bundle.getFloat(keyForField(FIELD_STAR_RATING), /* defaultValue= */ RATING_UNSET); + checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE); + int maxStars = bundle.getInt(FIELD_MAX_STARS, /* defaultValue= */ MAX_STARS_DEFAULT); + float starRating = bundle.getFloat(FIELD_STAR_RATING, /* defaultValue= */ RATING_UNSET); return starRating == RATING_UNSET ? new StarRating(maxStars) : new StarRating(maxStars, starRating); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java b/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java index e77e03bf8d..3e38c79057 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ThumbRating.java @@ -16,16 +16,11 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.Objects; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** A rating expressed as "thumbs up" or "thumbs down". */ public final class ThumbRating extends Rating { @@ -77,21 +72,15 @@ public final class ThumbRating extends Rating { private static final @RatingType int TYPE = RATING_TYPE_THUMB; - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_THUMBS_UP}) - private @interface FieldNumber {} - - private static final int FIELD_RATED = 1; - private static final int FIELD_IS_THUMBS_UP = 2; + private static final String FIELD_RATED = Util.intToStringMaxRadix(1); + private static final String FIELD_IS_THUMBS_UP = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE); - bundle.putBoolean(keyForField(FIELD_RATED), rated); - bundle.putBoolean(keyForField(FIELD_IS_THUMBS_UP), isThumbsUp); + bundle.putInt(FIELD_RATING_TYPE, TYPE); + bundle.putBoolean(FIELD_RATED, rated); + bundle.putBoolean(FIELD_IS_THUMBS_UP, isThumbsUp); return bundle; } @@ -99,17 +88,10 @@ public final class ThumbRating extends Rating { public static final Creator CREATOR = ThumbRating::fromBundle; private static ThumbRating fromBundle(Bundle bundle) { - checkArgument( - bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET) - == TYPE); - boolean rated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false); + checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE); + boolean rated = bundle.getBoolean(FIELD_RATED, /* defaultValue= */ false); return rated - ? new ThumbRating( - bundle.getBoolean(keyForField(FIELD_IS_THUMBS_UP), /* defaultValue= */ false)) + ? new ThumbRating(bundle.getBoolean(FIELD_IS_THUMBS_UP, /* defaultValue= */ false)) : new ThumbRating(); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 8571f870f2..51df3f0818 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -20,14 +20,12 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.Math.max; import static java.lang.Math.min; -import static java.lang.annotation.ElementType.TYPE_USE; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.util.Pair; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.util.Assertions; @@ -36,10 +34,6 @@ import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.InlineMe; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; @@ -419,39 +413,20 @@ public abstract class Timeline implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_MEDIA_ITEM, - FIELD_PRESENTATION_START_TIME_MS, - FIELD_WINDOW_START_TIME_MS, - FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, - FIELD_IS_SEEKABLE, - FIELD_IS_DYNAMIC, - FIELD_LIVE_CONFIGURATION, - FIELD_IS_PLACEHOLDER, - FIELD_DEFAULT_POSITION_US, - FIELD_DURATION_US, - FIELD_FIRST_PERIOD_INDEX, - FIELD_LAST_PERIOD_INDEX, - FIELD_POSITION_IN_FIRST_PERIOD_US, - }) - private @interface FieldNumber {} - - private static final int FIELD_MEDIA_ITEM = 1; - private static final int FIELD_PRESENTATION_START_TIME_MS = 2; - private static final int FIELD_WINDOW_START_TIME_MS = 3; - private static final int FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = 4; - private static final int FIELD_IS_SEEKABLE = 5; - private static final int FIELD_IS_DYNAMIC = 6; - private static final int FIELD_LIVE_CONFIGURATION = 7; - private static final int FIELD_IS_PLACEHOLDER = 8; - private static final int FIELD_DEFAULT_POSITION_US = 9; - private static final int FIELD_DURATION_US = 10; - private static final int FIELD_FIRST_PERIOD_INDEX = 11; - private static final int FIELD_LAST_PERIOD_INDEX = 12; - private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13; + private static final String FIELD_MEDIA_ITEM = Util.intToStringMaxRadix(1); + private static final String FIELD_PRESENTATION_START_TIME_MS = Util.intToStringMaxRadix(2); + private static final String FIELD_WINDOW_START_TIME_MS = Util.intToStringMaxRadix(3); + private static final String FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = + Util.intToStringMaxRadix(4); + private static final String FIELD_IS_SEEKABLE = Util.intToStringMaxRadix(5); + private static final String FIELD_IS_DYNAMIC = Util.intToStringMaxRadix(6); + private static final String FIELD_LIVE_CONFIGURATION = Util.intToStringMaxRadix(7); + private static final String FIELD_IS_PLACEHOLDER = Util.intToStringMaxRadix(8); + private static final String FIELD_DEFAULT_POSITION_US = Util.intToStringMaxRadix(9); + private static final String FIELD_DURATION_US = Util.intToStringMaxRadix(10); + private static final String FIELD_FIRST_PERIOD_INDEX = Util.intToStringMaxRadix(11); + private static final String FIELD_LAST_PERIOD_INDEX = Util.intToStringMaxRadix(12); + private static final String FIELD_POSITION_IN_FIRST_PERIOD_US = Util.intToStringMaxRadix(13); /** * Returns a {@link Bundle} representing the information stored in this object. @@ -465,46 +440,45 @@ public abstract class Timeline implements Bundleable { public Bundle toBundle(boolean excludeMediaItem) { Bundle bundle = new Bundle(); if (!excludeMediaItem) { - bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle()); + bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); } if (presentationStartTimeMs != C.TIME_UNSET) { - bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs); + bundle.putLong(FIELD_PRESENTATION_START_TIME_MS, presentationStartTimeMs); } if (windowStartTimeMs != C.TIME_UNSET) { - bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs); + bundle.putLong(FIELD_WINDOW_START_TIME_MS, windowStartTimeMs); } if (elapsedRealtimeEpochOffsetMs != C.TIME_UNSET) { - bundle.putLong( - keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs); + bundle.putLong(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, elapsedRealtimeEpochOffsetMs); } if (isSeekable) { - bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable); + bundle.putBoolean(FIELD_IS_SEEKABLE, isSeekable); } if (isDynamic) { - bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic); + bundle.putBoolean(FIELD_IS_DYNAMIC, isDynamic); } @Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration; if (liveConfiguration != null) { - bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); + bundle.putBundle(FIELD_LIVE_CONFIGURATION, liveConfiguration.toBundle()); } if (isPlaceholder) { - bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder); + bundle.putBoolean(FIELD_IS_PLACEHOLDER, isPlaceholder); } if (defaultPositionUs != 0) { - bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs); + bundle.putLong(FIELD_DEFAULT_POSITION_US, defaultPositionUs); } if (durationUs != C.TIME_UNSET) { - bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + bundle.putLong(FIELD_DURATION_US, durationUs); } if (firstPeriodIndex != 0) { - bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex); + bundle.putInt(FIELD_FIRST_PERIOD_INDEX, firstPeriodIndex); } if (lastPeriodIndex != 0) { - bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex); + bundle.putInt(FIELD_LAST_PERIOD_INDEX, lastPeriodIndex); } if (positionInFirstPeriodUs != 0) { - bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs); + bundle.putLong(FIELD_POSITION_IN_FIRST_PERIOD_US, positionInFirstPeriodUs); } return bundle; } @@ -531,42 +505,31 @@ public abstract class Timeline implements Bundleable { public static final Creator CREATOR = Window::fromBundle; private static Window fromBundle(Bundle bundle) { - @Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)); + @Nullable Bundle mediaItemBundle = bundle.getBundle(FIELD_MEDIA_ITEM); @Nullable MediaItem mediaItem = mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : MediaItem.EMPTY; long presentationStartTimeMs = - bundle.getLong( - keyForField(FIELD_PRESENTATION_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET); + bundle.getLong(FIELD_PRESENTATION_START_TIME_MS, /* defaultValue= */ C.TIME_UNSET); long windowStartTimeMs = - bundle.getLong(keyForField(FIELD_WINDOW_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET); + bundle.getLong(FIELD_WINDOW_START_TIME_MS, /* defaultValue= */ C.TIME_UNSET); long elapsedRealtimeEpochOffsetMs = - bundle.getLong( - keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), - /* defaultValue= */ C.TIME_UNSET); - boolean isSeekable = - bundle.getBoolean(keyForField(FIELD_IS_SEEKABLE), /* defaultValue= */ false); - boolean isDynamic = - bundle.getBoolean(keyForField(FIELD_IS_DYNAMIC), /* defaultValue= */ false); - @Nullable - Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION)); + bundle.getLong(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, /* defaultValue= */ C.TIME_UNSET); + boolean isSeekable = bundle.getBoolean(FIELD_IS_SEEKABLE, /* defaultValue= */ false); + boolean isDynamic = bundle.getBoolean(FIELD_IS_DYNAMIC, /* defaultValue= */ false); + @Nullable Bundle liveConfigurationBundle = bundle.getBundle(FIELD_LIVE_CONFIGURATION); @Nullable MediaItem.LiveConfiguration liveConfiguration = liveConfigurationBundle != null ? MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle) : null; - boolean isPlaceHolder = - bundle.getBoolean(keyForField(FIELD_IS_PLACEHOLDER), /* defaultValue= */ false); - long defaultPositionUs = - bundle.getLong(keyForField(FIELD_DEFAULT_POSITION_US), /* defaultValue= */ 0); - long durationUs = - bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); - int firstPeriodIndex = - bundle.getInt(keyForField(FIELD_FIRST_PERIOD_INDEX), /* defaultValue= */ 0); - int lastPeriodIndex = - bundle.getInt(keyForField(FIELD_LAST_PERIOD_INDEX), /* defaultValue= */ 0); + boolean isPlaceHolder = bundle.getBoolean(FIELD_IS_PLACEHOLDER, /* defaultValue= */ false); + long defaultPositionUs = bundle.getLong(FIELD_DEFAULT_POSITION_US, /* defaultValue= */ 0); + long durationUs = bundle.getLong(FIELD_DURATION_US, /* defaultValue= */ C.TIME_UNSET); + int firstPeriodIndex = bundle.getInt(FIELD_FIRST_PERIOD_INDEX, /* defaultValue= */ 0); + int lastPeriodIndex = bundle.getInt(FIELD_LAST_PERIOD_INDEX, /* defaultValue= */ 0); long positionInFirstPeriodUs = - bundle.getLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), /* defaultValue= */ 0); + bundle.getLong(FIELD_POSITION_IN_FIRST_PERIOD_US, /* defaultValue= */ 0); Window window = new Window(); window.set( @@ -587,10 +550,6 @@ public abstract class Timeline implements Bundleable { window.isPlaceholder = isPlaceHolder; return window; } - - private static String keyForField(@Window.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -937,23 +896,11 @@ public abstract class Timeline implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_WINDOW_INDEX, - FIELD_DURATION_US, - FIELD_POSITION_IN_WINDOW_US, - FIELD_PLACEHOLDER, - FIELD_AD_PLAYBACK_STATE - }) - private @interface FieldNumber {} - - private static final int FIELD_WINDOW_INDEX = 0; - private static final int FIELD_DURATION_US = 1; - private static final int FIELD_POSITION_IN_WINDOW_US = 2; - private static final int FIELD_PLACEHOLDER = 3; - private static final int FIELD_AD_PLAYBACK_STATE = 4; + private static final String FIELD_WINDOW_INDEX = Util.intToStringMaxRadix(0); + private static final String FIELD_DURATION_US = Util.intToStringMaxRadix(1); + private static final String FIELD_POSITION_IN_WINDOW_US = Util.intToStringMaxRadix(2); + private static final String FIELD_PLACEHOLDER = Util.intToStringMaxRadix(3); + private static final String FIELD_AD_PLAYBACK_STATE = Util.intToStringMaxRadix(4); /** * {@inheritDoc} @@ -965,19 +912,19 @@ public abstract class Timeline implements Bundleable { public Bundle toBundle() { Bundle bundle = new Bundle(); if (windowIndex != 0) { - bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex); + bundle.putInt(FIELD_WINDOW_INDEX, windowIndex); } if (durationUs != C.TIME_UNSET) { - bundle.putLong(keyForField(FIELD_DURATION_US), durationUs); + bundle.putLong(FIELD_DURATION_US, durationUs); } if (positionInWindowUs != 0) { - bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs); + bundle.putLong(FIELD_POSITION_IN_WINDOW_US, positionInWindowUs); } if (isPlaceholder) { - bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder); + bundle.putBoolean(FIELD_PLACEHOLDER, isPlaceholder); } if (!adPlaybackState.equals(AdPlaybackState.NONE)) { - bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle()); + bundle.putBundle(FIELD_AD_PLAYBACK_STATE, adPlaybackState.toBundle()); } return bundle; } @@ -990,15 +937,11 @@ public abstract class Timeline implements Bundleable { public static final Creator CREATOR = Period::fromBundle; private static Period fromBundle(Bundle bundle) { - int windowIndex = bundle.getInt(keyForField(FIELD_WINDOW_INDEX), /* defaultValue= */ 0); - long durationUs = - bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET); - long positionInWindowUs = - bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0); - boolean isPlaceholder = - bundle.getBoolean(keyForField(FIELD_PLACEHOLDER), /* defaultValue= */ false); - @Nullable - Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE)); + int windowIndex = bundle.getInt(FIELD_WINDOW_INDEX, /* defaultValue= */ 0); + long durationUs = bundle.getLong(FIELD_DURATION_US, /* defaultValue= */ C.TIME_UNSET); + long positionInWindowUs = bundle.getLong(FIELD_POSITION_IN_WINDOW_US, /* defaultValue= */ 0); + boolean isPlaceholder = bundle.getBoolean(FIELD_PLACEHOLDER, /* defaultValue= */ false); + @Nullable Bundle adPlaybackStateBundle = bundle.getBundle(FIELD_AD_PLAYBACK_STATE); AdPlaybackState adPlaybackState = adPlaybackStateBundle != null ? AdPlaybackState.CREATOR.fromBundle(adPlaybackStateBundle) @@ -1015,10 +958,6 @@ public abstract class Timeline implements Bundleable { isPlaceholder); return period; } - - private static String keyForField(@Period.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** An empty timeline. */ @@ -1435,19 +1374,9 @@ public abstract class Timeline implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_WINDOWS, - FIELD_PERIODS, - FIELD_SHUFFLED_WINDOW_INDICES, - }) - private @interface FieldNumber {} - - private static final int FIELD_WINDOWS = 0; - private static final int FIELD_PERIODS = 1; - private static final int FIELD_SHUFFLED_WINDOW_INDICES = 2; + private static final String FIELD_WINDOWS = Util.intToStringMaxRadix(0); + private static final String FIELD_PERIODS = Util.intToStringMaxRadix(1); + private static final String FIELD_SHUFFLED_WINDOW_INDICES = Util.intToStringMaxRadix(2); /** * {@inheritDoc} @@ -1486,11 +1415,9 @@ public abstract class Timeline implements Bundleable { } Bundle bundle = new Bundle(); - BundleUtil.putBinder( - bundle, keyForField(FIELD_WINDOWS), new BundleListRetriever(windowBundles)); - BundleUtil.putBinder( - bundle, keyForField(FIELD_PERIODS), new BundleListRetriever(periodBundles)); - bundle.putIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES), shuffledWindowIndices); + BundleUtil.putBinder(bundle, FIELD_WINDOWS, new BundleListRetriever(windowBundles)); + BundleUtil.putBinder(bundle, FIELD_PERIODS, new BundleListRetriever(periodBundles)); + bundle.putIntArray(FIELD_SHUFFLED_WINDOW_INDICES, shuffledWindowIndices); return bundle; } @@ -1517,13 +1444,10 @@ public abstract class Timeline implements Bundleable { private static Timeline fromBundle(Bundle bundle) { ImmutableList windows = - fromBundleListRetriever( - Window.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_WINDOWS))); + fromBundleListRetriever(Window.CREATOR, BundleUtil.getBinder(bundle, FIELD_WINDOWS)); ImmutableList periods = - fromBundleListRetriever( - Period.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_PERIODS))); - @Nullable - int[] shuffledWindowIndices = bundle.getIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES)); + fromBundleListRetriever(Period.CREATOR, BundleUtil.getBinder(bundle, FIELD_PERIODS)); + @Nullable int[] shuffledWindowIndices = bundle.getIntArray(FIELD_SHUFFLED_WINDOW_INDICES); return new RemotableTimeline( windows, periods, @@ -1545,10 +1469,6 @@ public abstract class Timeline implements Bundleable { return builder.build(); } - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - private static int[] generateUnshuffledIndices(int n) { int[] indices = new int[n]; for (int i = 0; i < n; i++) { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Tracks.java b/library/common/src/main/java/com/google/android/exoplayer2/Tracks.java index 32ddfb78af..ed7ff22822 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Tracks.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Tracks.java @@ -18,20 +18,15 @@ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.util.BundleableUtil; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.primitives.Booleans; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; @@ -219,29 +214,19 @@ public final class Tracks implements Bundleable { } // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TRACK_GROUP, - FIELD_TRACK_SUPPORT, - FIELD_TRACK_SELECTED, - FIELD_ADAPTIVE_SUPPORTED, - }) - private @interface FieldNumber {} - private static final int FIELD_TRACK_GROUP = 0; - private static final int FIELD_TRACK_SUPPORT = 1; - private static final int FIELD_TRACK_SELECTED = 3; - private static final int FIELD_ADAPTIVE_SUPPORTED = 4; + private static final String FIELD_TRACK_GROUP = Util.intToStringMaxRadix(0); + private static final String FIELD_TRACK_SUPPORT = Util.intToStringMaxRadix(1); + private static final String FIELD_TRACK_SELECTED = Util.intToStringMaxRadix(3); + private static final String FIELD_ADAPTIVE_SUPPORTED = Util.intToStringMaxRadix(4); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle()); - bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport); - bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected); - bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported); + bundle.putBundle(FIELD_TRACK_GROUP, mediaTrackGroup.toBundle()); + bundle.putIntArray(FIELD_TRACK_SUPPORT, trackSupport); + bundle.putBooleanArray(FIELD_TRACK_SELECTED, trackSelected); + bundle.putBoolean(FIELD_ADAPTIVE_SUPPORTED, adaptiveSupported); return bundle; } @@ -250,23 +235,16 @@ public final class Tracks implements Bundleable { bundle -> { // Can't create a Tracks.Group without a TrackGroup TrackGroup trackGroup = - TrackGroup.CREATOR.fromBundle( - checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)))); + TrackGroup.CREATOR.fromBundle(checkNotNull(bundle.getBundle(FIELD_TRACK_GROUP))); final @C.FormatSupport int[] trackSupport = MoreObjects.firstNonNull( - bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]); + bundle.getIntArray(FIELD_TRACK_SUPPORT), new int[trackGroup.length]); boolean[] selected = MoreObjects.firstNonNull( - bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)), - new boolean[trackGroup.length]); - boolean adaptiveSupported = - bundle.getBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), false); + bundle.getBooleanArray(FIELD_TRACK_SELECTED), new boolean[trackGroup.length]); + boolean adaptiveSupported = bundle.getBoolean(FIELD_ADAPTIVE_SUPPORTED, false); return new Group(trackGroup, adaptiveSupported, trackSupport, selected); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** Empty tracks. */ @@ -379,36 +357,23 @@ public final class Tracks implements Bundleable { } // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TRACK_GROUPS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TRACK_GROUPS = 0; + private static final String FIELD_TRACK_GROUPS = Util.intToStringMaxRadix(0); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(keyForField(FIELD_TRACK_GROUPS), toBundleArrayList(groups)); + bundle.putParcelableArrayList(FIELD_TRACK_GROUPS, toBundleArrayList(groups)); return bundle; } /** Object that can restore tracks from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> { - @Nullable - List groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS)); + @Nullable List groupBundles = bundle.getParcelableArrayList(FIELD_TRACK_GROUPS); List groups = groupBundles == null ? ImmutableList.of() : BundleableUtil.fromBundleList(Group.CREATOR, groupBundles); return new Tracks(groups); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java index 7642f44369..aa181119bd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java @@ -15,21 +15,14 @@ */ package com.google.android.exoplayer2.audio; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; import androidx.annotation.DoNotInline; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** * Attributes for audio playback, which configure the underlying platform {@link @@ -206,32 +199,20 @@ public final class AudioAttributes implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_CONTENT_TYPE, - FIELD_FLAGS, - FIELD_USAGE, - FIELD_ALLOWED_CAPTURE_POLICY, - FIELD_SPATIALIZATION_BEHAVIOR - }) - private @interface FieldNumber {} - - private static final int FIELD_CONTENT_TYPE = 0; - private static final int FIELD_FLAGS = 1; - private static final int FIELD_USAGE = 2; - private static final int FIELD_ALLOWED_CAPTURE_POLICY = 3; - private static final int FIELD_SPATIALIZATION_BEHAVIOR = 4; + private static final String FIELD_CONTENT_TYPE = Util.intToStringMaxRadix(0); + private static final String FIELD_FLAGS = Util.intToStringMaxRadix(1); + private static final String FIELD_USAGE = Util.intToStringMaxRadix(2); + private static final String FIELD_ALLOWED_CAPTURE_POLICY = Util.intToStringMaxRadix(3); + private static final String FIELD_SPATIALIZATION_BEHAVIOR = Util.intToStringMaxRadix(4); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_CONTENT_TYPE), contentType); - bundle.putInt(keyForField(FIELD_FLAGS), flags); - bundle.putInt(keyForField(FIELD_USAGE), usage); - bundle.putInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY), allowedCapturePolicy); - bundle.putInt(keyForField(FIELD_SPATIALIZATION_BEHAVIOR), spatializationBehavior); + bundle.putInt(FIELD_CONTENT_TYPE, contentType); + bundle.putInt(FIELD_FLAGS, flags); + bundle.putInt(FIELD_USAGE, usage); + bundle.putInt(FIELD_ALLOWED_CAPTURE_POLICY, allowedCapturePolicy); + bundle.putInt(FIELD_SPATIALIZATION_BEHAVIOR, spatializationBehavior); return bundle; } @@ -239,29 +220,24 @@ public final class AudioAttributes implements Bundleable { public static final Creator CREATOR = bundle -> { Builder builder = new Builder(); - if (bundle.containsKey(keyForField(FIELD_CONTENT_TYPE))) { - builder.setContentType(bundle.getInt(keyForField(FIELD_CONTENT_TYPE))); + if (bundle.containsKey(FIELD_CONTENT_TYPE)) { + builder.setContentType(bundle.getInt(FIELD_CONTENT_TYPE)); } - if (bundle.containsKey(keyForField(FIELD_FLAGS))) { - builder.setFlags(bundle.getInt(keyForField(FIELD_FLAGS))); + if (bundle.containsKey(FIELD_FLAGS)) { + builder.setFlags(bundle.getInt(FIELD_FLAGS)); } - if (bundle.containsKey(keyForField(FIELD_USAGE))) { - builder.setUsage(bundle.getInt(keyForField(FIELD_USAGE))); + if (bundle.containsKey(FIELD_USAGE)) { + builder.setUsage(bundle.getInt(FIELD_USAGE)); } - if (bundle.containsKey(keyForField(FIELD_ALLOWED_CAPTURE_POLICY))) { - builder.setAllowedCapturePolicy(bundle.getInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY))); + if (bundle.containsKey(FIELD_ALLOWED_CAPTURE_POLICY)) { + builder.setAllowedCapturePolicy(bundle.getInt(FIELD_ALLOWED_CAPTURE_POLICY)); } - if (bundle.containsKey(keyForField(FIELD_SPATIALIZATION_BEHAVIOR))) { - builder.setSpatializationBehavior( - bundle.getInt(keyForField(FIELD_SPATIALIZATION_BEHAVIOR))); + if (bundle.containsKey(FIELD_SPATIALIZATION_BEHAVIOR)) { + builder.setSpatializationBehavior(bundle.getInt(FIELD_SPATIALIZATION_BEHAVIOR)); } return builder.build(); }; - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - @RequiresApi(29) private static final class Api29 { @DoNotInline diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index c2be277d43..e0016540dd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -16,11 +16,9 @@ package com.google.android.exoplayer2.source; import static com.google.android.exoplayer2.util.Assertions.checkArgument; -import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.CheckResult; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; @@ -29,11 +27,8 @@ import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.util.BundleableUtil; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -164,15 +159,8 @@ public final class TrackGroup implements Bundleable { } // Bundleable implementation. - - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_FORMATS, FIELD_ID}) - private @interface FieldNumber {} - - private static final int FIELD_FORMATS = 0; - private static final int FIELD_ID = 1; + private static final String FIELD_FORMATS = Util.intToStringMaxRadix(0); + private static final String FIELD_ID = Util.intToStringMaxRadix(1); @Override public Bundle toBundle() { @@ -181,28 +169,23 @@ public final class TrackGroup implements Bundleable { for (Format format : formats) { arrayList.add(format.toBundle(/* excludeMetadata= */ true)); } - bundle.putParcelableArrayList(keyForField(FIELD_FORMATS), arrayList); - bundle.putString(keyForField(FIELD_ID), id); + bundle.putParcelableArrayList(FIELD_FORMATS, arrayList); + bundle.putString(FIELD_ID, id); return bundle; } /** Object that can restore {@code TrackGroup} from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> { - @Nullable - List formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)); + @Nullable List formatBundles = bundle.getParcelableArrayList(FIELD_FORMATS); List formats = formatBundles == null ? ImmutableList.of() : BundleableUtil.fromBundleList(Format.CREATOR, formatBundles); - String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ ""); + String id = bundle.getString(FIELD_ID, /* defaultValue= */ ""); return new TrackGroup(id, formats.toArray(new Format[0])); }; - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - private void verifyCorrectness() { // TrackGroups should only contain tracks with exactly the same content (but in different // qualities). We only log an error instead of throwing to not break backwards-compatibility for diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 719a215244..6054def4ff 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -460,44 +460,29 @@ public final class AdPlaybackState implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TIME_US, - FIELD_COUNT, - FIELD_URIS, - FIELD_STATES, - FIELD_DURATIONS_US, - FIELD_CONTENT_RESUME_OFFSET_US, - FIELD_IS_SERVER_SIDE_INSERTED, - FIELD_ORIGINAL_COUNT - }) - private @interface FieldNumber {} - - private static final int FIELD_TIME_US = 0; - private static final int FIELD_COUNT = 1; - private static final int FIELD_URIS = 2; - private static final int FIELD_STATES = 3; - private static final int FIELD_DURATIONS_US = 4; - private static final int FIELD_CONTENT_RESUME_OFFSET_US = 5; - private static final int FIELD_IS_SERVER_SIDE_INSERTED = 6; - private static final int FIELD_ORIGINAL_COUNT = 7; + private static final String FIELD_TIME_US = Util.intToStringMaxRadix(0); + private static final String FIELD_COUNT = Util.intToStringMaxRadix(1); + private static final String FIELD_URIS = Util.intToStringMaxRadix(2); + private static final String FIELD_STATES = Util.intToStringMaxRadix(3); + private static final String FIELD_DURATIONS_US = Util.intToStringMaxRadix(4); + private static final String FIELD_CONTENT_RESUME_OFFSET_US = Util.intToStringMaxRadix(5); + private static final String FIELD_IS_SERVER_SIDE_INSERTED = Util.intToStringMaxRadix(6); + private static final String FIELD_ORIGINAL_COUNT = Util.intToStringMaxRadix(7); // putParcelableArrayList actually supports null elements. @SuppressWarnings("nullness:argument") @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putLong(keyForField(FIELD_TIME_US), timeUs); - bundle.putInt(keyForField(FIELD_COUNT), count); - bundle.putInt(keyForField(FIELD_ORIGINAL_COUNT), originalCount); + bundle.putLong(FIELD_TIME_US, timeUs); + bundle.putInt(FIELD_COUNT, count); + bundle.putInt(FIELD_ORIGINAL_COUNT, originalCount); bundle.putParcelableArrayList( - keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris))); - bundle.putIntArray(keyForField(FIELD_STATES), states); - bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs); - bundle.putLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US), contentResumeOffsetUs); - bundle.putBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED), isServerSideInserted); + FIELD_URIS, new ArrayList<@NullableType Uri>(Arrays.asList(uris))); + bundle.putIntArray(FIELD_STATES, states); + bundle.putLongArray(FIELD_DURATIONS_US, durationsUs); + bundle.putLong(FIELD_CONTENT_RESUME_OFFSET_US, contentResumeOffsetUs); + bundle.putBoolean(FIELD_IS_SERVER_SIDE_INSERTED, isServerSideInserted); return bundle; } @@ -507,17 +492,16 @@ public final class AdPlaybackState implements Bundleable { // getParcelableArrayList may have null elements. @SuppressWarnings("nullness:type.argument") private static AdGroup fromBundle(Bundle bundle) { - long timeUs = bundle.getLong(keyForField(FIELD_TIME_US)); - int count = bundle.getInt(keyForField(FIELD_COUNT)); - int originalCount = bundle.getInt(keyForField(FIELD_ORIGINAL_COUNT)); - @Nullable - ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(keyForField(FIELD_URIS)); + long timeUs = bundle.getLong(FIELD_TIME_US); + int count = bundle.getInt(FIELD_COUNT); + int originalCount = bundle.getInt(FIELD_ORIGINAL_COUNT); + @Nullable ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(FIELD_URIS); @Nullable @AdState - int[] states = bundle.getIntArray(keyForField(FIELD_STATES)); - @Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US)); - long contentResumeOffsetUs = bundle.getLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US)); - boolean isServerSideInserted = bundle.getBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED)); + int[] states = bundle.getIntArray(FIELD_STATES); + @Nullable long[] durationsUs = bundle.getLongArray(FIELD_DURATIONS_US); + long contentResumeOffsetUs = bundle.getLong(FIELD_CONTENT_RESUME_OFFSET_US); + boolean isServerSideInserted = bundle.getBoolean(FIELD_IS_SERVER_SIDE_INSERTED); return new AdGroup( timeUs, count, @@ -528,10 +512,6 @@ public final class AdPlaybackState implements Bundleable { contentResumeOffsetUs, isServerSideInserted); } - - private static String keyForField(@AdGroup.FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -1122,21 +1102,10 @@ public final class AdPlaybackState implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_AD_GROUPS, - FIELD_AD_RESUME_POSITION_US, - FIELD_CONTENT_DURATION_US, - FIELD_REMOVED_AD_GROUP_COUNT - }) - private @interface FieldNumber {} - - private static final int FIELD_AD_GROUPS = 1; - private static final int FIELD_AD_RESUME_POSITION_US = 2; - private static final int FIELD_CONTENT_DURATION_US = 3; - private static final int FIELD_REMOVED_AD_GROUP_COUNT = 4; + private static final String FIELD_AD_GROUPS = Util.intToStringMaxRadix(1); + private static final String FIELD_AD_RESUME_POSITION_US = Util.intToStringMaxRadix(2); + private static final String FIELD_CONTENT_DURATION_US = Util.intToStringMaxRadix(3); + private static final String FIELD_REMOVED_AD_GROUP_COUNT = Util.intToStringMaxRadix(4); /** * {@inheritDoc} @@ -1153,16 +1122,16 @@ public final class AdPlaybackState implements Bundleable { adGroupBundleList.add(adGroup.toBundle()); } if (!adGroupBundleList.isEmpty()) { - bundle.putParcelableArrayList(keyForField(FIELD_AD_GROUPS), adGroupBundleList); + bundle.putParcelableArrayList(FIELD_AD_GROUPS, adGroupBundleList); } if (adResumePositionUs != NONE.adResumePositionUs) { - bundle.putLong(keyForField(FIELD_AD_RESUME_POSITION_US), adResumePositionUs); + bundle.putLong(FIELD_AD_RESUME_POSITION_US, adResumePositionUs); } if (contentDurationUs != NONE.contentDurationUs) { - bundle.putLong(keyForField(FIELD_CONTENT_DURATION_US), contentDurationUs); + bundle.putLong(FIELD_CONTENT_DURATION_US, contentDurationUs); } if (removedAdGroupCount != NONE.removedAdGroupCount) { - bundle.putInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT), removedAdGroupCount); + bundle.putInt(FIELD_REMOVED_AD_GROUP_COUNT, removedAdGroupCount); } return bundle; } @@ -1175,9 +1144,7 @@ public final class AdPlaybackState implements Bundleable { public static final Bundleable.Creator CREATOR = AdPlaybackState::fromBundle; private static AdPlaybackState fromBundle(Bundle bundle) { - @Nullable - ArrayList adGroupBundleList = - bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS)); + @Nullable ArrayList adGroupBundleList = bundle.getParcelableArrayList(FIELD_AD_GROUPS); @Nullable AdGroup[] adGroups; if (adGroupBundleList == null) { adGroups = new AdGroup[0]; @@ -1188,23 +1155,15 @@ public final class AdPlaybackState implements Bundleable { } } long adResumePositionUs = - bundle.getLong( - keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ NONE.adResumePositionUs); + bundle.getLong(FIELD_AD_RESUME_POSITION_US, /* defaultValue= */ NONE.adResumePositionUs); long contentDurationUs = - bundle.getLong( - keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ NONE.contentDurationUs); + bundle.getLong(FIELD_CONTENT_DURATION_US, /* defaultValue= */ NONE.contentDurationUs); int removedAdGroupCount = - bundle.getInt( - keyForField(FIELD_REMOVED_AD_GROUP_COUNT), - /* defaultValue= */ NONE.removedAdGroupCount); + bundle.getInt(FIELD_REMOVED_AD_GROUP_COUNT, /* defaultValue= */ NONE.removedAdGroupCount); return new AdPlaybackState( /* adsId= */ null, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount); } - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - private static AdGroup[] createEmptyAdGroups(long[] adGroupTimesUs) { AdGroup[] adGroups = new AdGroup[adGroupTimesUs.length]; for (int i = 0; i < adGroups.length; i++) { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java index 8db6bc1881..d96fb263f6 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -34,6 +34,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.annotation.Documented; @@ -970,68 +971,44 @@ public final class Cue implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TEXT, - FIELD_TEXT_ALIGNMENT, - FIELD_MULTI_ROW_ALIGNMENT, - FIELD_BITMAP, - FIELD_LINE, - FIELD_LINE_TYPE, - FIELD_LINE_ANCHOR, - FIELD_POSITION, - FIELD_POSITION_ANCHOR, - FIELD_TEXT_SIZE_TYPE, - FIELD_TEXT_SIZE, - FIELD_SIZE, - FIELD_BITMAP_HEIGHT, - FIELD_WINDOW_COLOR, - FIELD_WINDOW_COLOR_SET, - FIELD_VERTICAL_TYPE, - FIELD_SHEAR_DEGREES - }) - private @interface FieldNumber {} - - private static final int FIELD_TEXT = 0; - private static final int FIELD_TEXT_ALIGNMENT = 1; - private static final int FIELD_MULTI_ROW_ALIGNMENT = 2; - private static final int FIELD_BITMAP = 3; - private static final int FIELD_LINE = 4; - private static final int FIELD_LINE_TYPE = 5; - private static final int FIELD_LINE_ANCHOR = 6; - private static final int FIELD_POSITION = 7; - private static final int FIELD_POSITION_ANCHOR = 8; - private static final int FIELD_TEXT_SIZE_TYPE = 9; - private static final int FIELD_TEXT_SIZE = 10; - private static final int FIELD_SIZE = 11; - private static final int FIELD_BITMAP_HEIGHT = 12; - private static final int FIELD_WINDOW_COLOR = 13; - private static final int FIELD_WINDOW_COLOR_SET = 14; - private static final int FIELD_VERTICAL_TYPE = 15; - private static final int FIELD_SHEAR_DEGREES = 16; + private static final String FIELD_TEXT = Util.intToStringMaxRadix(0); + private static final String FIELD_TEXT_ALIGNMENT = Util.intToStringMaxRadix(1); + private static final String FIELD_MULTI_ROW_ALIGNMENT = Util.intToStringMaxRadix(2); + private static final String FIELD_BITMAP = Util.intToStringMaxRadix(3); + private static final String FIELD_LINE = Util.intToStringMaxRadix(4); + private static final String FIELD_LINE_TYPE = Util.intToStringMaxRadix(5); + private static final String FIELD_LINE_ANCHOR = Util.intToStringMaxRadix(6); + private static final String FIELD_POSITION = Util.intToStringMaxRadix(7); + private static final String FIELD_POSITION_ANCHOR = Util.intToStringMaxRadix(8); + private static final String FIELD_TEXT_SIZE_TYPE = Util.intToStringMaxRadix(9); + private static final String FIELD_TEXT_SIZE = Util.intToStringMaxRadix(10); + private static final String FIELD_SIZE = Util.intToStringMaxRadix(11); + private static final String FIELD_BITMAP_HEIGHT = Util.intToStringMaxRadix(12); + private static final String FIELD_WINDOW_COLOR = Util.intToStringMaxRadix(13); + private static final String FIELD_WINDOW_COLOR_SET = Util.intToStringMaxRadix(14); + private static final String FIELD_VERTICAL_TYPE = Util.intToStringMaxRadix(15); + private static final String FIELD_SHEAR_DEGREES = Util.intToStringMaxRadix(16); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putCharSequence(keyForField(FIELD_TEXT), text); - bundle.putSerializable(keyForField(FIELD_TEXT_ALIGNMENT), textAlignment); - bundle.putSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT), multiRowAlignment); - bundle.putParcelable(keyForField(FIELD_BITMAP), bitmap); - bundle.putFloat(keyForField(FIELD_LINE), line); - bundle.putInt(keyForField(FIELD_LINE_TYPE), lineType); - bundle.putInt(keyForField(FIELD_LINE_ANCHOR), lineAnchor); - bundle.putFloat(keyForField(FIELD_POSITION), position); - bundle.putInt(keyForField(FIELD_POSITION_ANCHOR), positionAnchor); - bundle.putInt(keyForField(FIELD_TEXT_SIZE_TYPE), textSizeType); - bundle.putFloat(keyForField(FIELD_TEXT_SIZE), textSize); - bundle.putFloat(keyForField(FIELD_SIZE), size); - bundle.putFloat(keyForField(FIELD_BITMAP_HEIGHT), bitmapHeight); - bundle.putBoolean(keyForField(FIELD_WINDOW_COLOR_SET), windowColorSet); - bundle.putInt(keyForField(FIELD_WINDOW_COLOR), windowColor); - bundle.putInt(keyForField(FIELD_VERTICAL_TYPE), verticalType); - bundle.putFloat(keyForField(FIELD_SHEAR_DEGREES), shearDegrees); + bundle.putCharSequence(FIELD_TEXT, text); + bundle.putSerializable(FIELD_TEXT_ALIGNMENT, textAlignment); + bundle.putSerializable(FIELD_MULTI_ROW_ALIGNMENT, multiRowAlignment); + bundle.putParcelable(FIELD_BITMAP, bitmap); + bundle.putFloat(FIELD_LINE, line); + bundle.putInt(FIELD_LINE_TYPE, lineType); + bundle.putInt(FIELD_LINE_ANCHOR, lineAnchor); + bundle.putFloat(FIELD_POSITION, position); + bundle.putInt(FIELD_POSITION_ANCHOR, positionAnchor); + bundle.putInt(FIELD_TEXT_SIZE_TYPE, textSizeType); + bundle.putFloat(FIELD_TEXT_SIZE, textSize); + bundle.putFloat(FIELD_SIZE, size); + bundle.putFloat(FIELD_BITMAP_HEIGHT, bitmapHeight); + bundle.putBoolean(FIELD_WINDOW_COLOR_SET, windowColorSet); + bundle.putInt(FIELD_WINDOW_COLOR, windowColor); + bundle.putInt(FIELD_VERTICAL_TYPE, verticalType); + bundle.putFloat(FIELD_SHEAR_DEGREES, shearDegrees); return bundle; } @@ -1039,67 +1016,56 @@ public final class Cue implements Bundleable { private static final Cue fromBundle(Bundle bundle) { Builder builder = new Builder(); - @Nullable CharSequence text = bundle.getCharSequence(keyForField(FIELD_TEXT)); + @Nullable CharSequence text = bundle.getCharSequence(FIELD_TEXT); if (text != null) { builder.setText(text); } - @Nullable - Alignment textAlignment = (Alignment) bundle.getSerializable(keyForField(FIELD_TEXT_ALIGNMENT)); + @Nullable Alignment textAlignment = (Alignment) bundle.getSerializable(FIELD_TEXT_ALIGNMENT); if (textAlignment != null) { builder.setTextAlignment(textAlignment); } @Nullable - Alignment multiRowAlignment = - (Alignment) bundle.getSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT)); + Alignment multiRowAlignment = (Alignment) bundle.getSerializable(FIELD_MULTI_ROW_ALIGNMENT); if (multiRowAlignment != null) { builder.setMultiRowAlignment(multiRowAlignment); } - @Nullable Bitmap bitmap = bundle.getParcelable(keyForField(FIELD_BITMAP)); + @Nullable Bitmap bitmap = bundle.getParcelable(FIELD_BITMAP); if (bitmap != null) { builder.setBitmap(bitmap); } - if (bundle.containsKey(keyForField(FIELD_LINE)) - && bundle.containsKey(keyForField(FIELD_LINE_TYPE))) { - builder.setLine( - bundle.getFloat(keyForField(FIELD_LINE)), bundle.getInt(keyForField(FIELD_LINE_TYPE))); + if (bundle.containsKey(FIELD_LINE) && bundle.containsKey(FIELD_LINE_TYPE)) { + builder.setLine(bundle.getFloat(FIELD_LINE), bundle.getInt(FIELD_LINE_TYPE)); } - if (bundle.containsKey(keyForField(FIELD_LINE_ANCHOR))) { - builder.setLineAnchor(bundle.getInt(keyForField(FIELD_LINE_ANCHOR))); + if (bundle.containsKey(FIELD_LINE_ANCHOR)) { + builder.setLineAnchor(bundle.getInt(FIELD_LINE_ANCHOR)); } - if (bundle.containsKey(keyForField(FIELD_POSITION))) { - builder.setPosition(bundle.getFloat(keyForField(FIELD_POSITION))); + if (bundle.containsKey(FIELD_POSITION)) { + builder.setPosition(bundle.getFloat(FIELD_POSITION)); } - if (bundle.containsKey(keyForField(FIELD_POSITION_ANCHOR))) { - builder.setPositionAnchor(bundle.getInt(keyForField(FIELD_POSITION_ANCHOR))); + if (bundle.containsKey(FIELD_POSITION_ANCHOR)) { + builder.setPositionAnchor(bundle.getInt(FIELD_POSITION_ANCHOR)); } - if (bundle.containsKey(keyForField(FIELD_TEXT_SIZE)) - && bundle.containsKey(keyForField(FIELD_TEXT_SIZE_TYPE))) { - builder.setTextSize( - bundle.getFloat(keyForField(FIELD_TEXT_SIZE)), - bundle.getInt(keyForField(FIELD_TEXT_SIZE_TYPE))); + if (bundle.containsKey(FIELD_TEXT_SIZE) && bundle.containsKey(FIELD_TEXT_SIZE_TYPE)) { + builder.setTextSize(bundle.getFloat(FIELD_TEXT_SIZE), bundle.getInt(FIELD_TEXT_SIZE_TYPE)); } - if (bundle.containsKey(keyForField(FIELD_SIZE))) { - builder.setSize(bundle.getFloat(keyForField(FIELD_SIZE))); + if (bundle.containsKey(FIELD_SIZE)) { + builder.setSize(bundle.getFloat(FIELD_SIZE)); } - if (bundle.containsKey(keyForField(FIELD_BITMAP_HEIGHT))) { - builder.setBitmapHeight(bundle.getFloat(keyForField(FIELD_BITMAP_HEIGHT))); + if (bundle.containsKey(FIELD_BITMAP_HEIGHT)) { + builder.setBitmapHeight(bundle.getFloat(FIELD_BITMAP_HEIGHT)); } - if (bundle.containsKey(keyForField(FIELD_WINDOW_COLOR))) { - builder.setWindowColor(bundle.getInt(keyForField(FIELD_WINDOW_COLOR))); + if (bundle.containsKey(FIELD_WINDOW_COLOR)) { + builder.setWindowColor(bundle.getInt(FIELD_WINDOW_COLOR)); } - if (!bundle.getBoolean(keyForField(FIELD_WINDOW_COLOR_SET), /* defaultValue= */ false)) { + if (!bundle.getBoolean(FIELD_WINDOW_COLOR_SET, /* defaultValue= */ false)) { builder.clearWindowColor(); } - if (bundle.containsKey(keyForField(FIELD_VERTICAL_TYPE))) { - builder.setVerticalType(bundle.getInt(keyForField(FIELD_VERTICAL_TYPE))); + if (bundle.containsKey(FIELD_VERTICAL_TYPE)) { + builder.setVerticalType(bundle.getInt(FIELD_VERTICAL_TYPE)); } - if (bundle.containsKey(keyForField(FIELD_SHEAR_DEGREES))) { - builder.setShearDegrees(bundle.getFloat(keyForField(FIELD_SHEAR_DEGREES))); + if (bundle.containsKey(FIELD_SHEAR_DEGREES)) { + builder.setShearDegrees(bundle.getFloat(FIELD_SHEAR_DEGREES)); } return builder.build(); } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/text/CueGroup.java b/library/common/src/main/java/com/google/android/exoplayer2/text/CueGroup.java index a45d55aff3..32672539b7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/text/CueGroup.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/text/CueGroup.java @@ -15,20 +15,14 @@ */ package com.google.android.exoplayer2.text; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.graphics.Bitmap; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.BundleableUtil; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; @@ -63,40 +57,30 @@ public final class CueGroup implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({FIELD_CUES, FIELD_PRESENTATION_TIME_US}) - private @interface FieldNumber {} - - private static final int FIELD_CUES = 0; - private static final int FIELD_PRESENTATION_TIME_US = 1; + private static final String FIELD_CUES = Util.intToStringMaxRadix(0); + private static final String FIELD_PRESENTATION_TIME_US = Util.intToStringMaxRadix(1); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); bundle.putParcelableArrayList( - keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues))); - bundle.putLong(keyForField(FIELD_PRESENTATION_TIME_US), presentationTimeUs); + FIELD_CUES, BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues))); + bundle.putLong(FIELD_PRESENTATION_TIME_US, presentationTimeUs); return bundle; } public static final Creator CREATOR = CueGroup::fromBundle; private static final CueGroup fromBundle(Bundle bundle) { - @Nullable ArrayList cueBundles = bundle.getParcelableArrayList(keyForField(FIELD_CUES)); + @Nullable ArrayList cueBundles = bundle.getParcelableArrayList(FIELD_CUES); List cues = cueBundles == null ? ImmutableList.of() : BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles); - long presentationTimeUs = bundle.getLong(keyForField(FIELD_PRESENTATION_TIME_US)); + long presentationTimeUs = bundle.getLong(FIELD_PRESENTATION_TIME_US); return new CueGroup(cues, presentationTimeUs); } - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } - /** * Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues * between processes to prevent transferring too much data. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverride.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverride.java index ac5cbccc9e..26acbdde9b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverride.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverride.java @@ -20,16 +20,13 @@ import static java.util.Collections.max; import static java.util.Collections.min; import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -56,16 +53,8 @@ public final class TrackSelectionOverride implements Bundleable { /** The indices of tracks in a {@link TrackGroup} to be selected. */ public final ImmutableList trackIndices; - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - FIELD_TRACK_GROUP, - FIELD_TRACKS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TRACK_GROUP = 0; - private static final int FIELD_TRACKS = 1; + private static final String FIELD_TRACK_GROUP = Util.intToStringMaxRadix(0); + private static final String FIELD_TRACKS = Util.intToStringMaxRadix(1); /** * Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected. @@ -120,21 +109,17 @@ public final class TrackSelectionOverride implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle()); - bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices)); + bundle.putBundle(FIELD_TRACK_GROUP, mediaTrackGroup.toBundle()); + bundle.putIntArray(FIELD_TRACKS, Ints.toArray(trackIndices)); return bundle; } /** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> { - Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))); + Bundle trackGroupBundle = checkNotNull(bundle.getBundle(FIELD_TRACK_GROUP)); TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle); - int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS))); + int[] tracks = checkNotNull(bundle.getIntArray(FIELD_TRACKS)); return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks)); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index a25d0ac03a..bbcfde8d2a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -158,95 +158,71 @@ public class TrackSelectionParameters implements Bundleable { /** Creates a builder with the initial values specified in {@code bundle}. */ protected Builder(Bundle bundle) { // Video - maxVideoWidth = - bundle.getInt(keyForField(FIELD_MAX_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.maxVideoWidth); + maxVideoWidth = bundle.getInt(FIELD_MAX_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.maxVideoWidth); maxVideoHeight = - bundle.getInt( - keyForField(FIELD_MAX_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.maxVideoHeight); + bundle.getInt(FIELD_MAX_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.maxVideoHeight); maxVideoFrameRate = - bundle.getInt( - keyForField(FIELD_MAX_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate); + bundle.getInt(FIELD_MAX_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate); maxVideoBitrate = - bundle.getInt( - keyForField(FIELD_MAX_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate); - minVideoWidth = - bundle.getInt(keyForField(FIELD_MIN_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.minVideoWidth); + bundle.getInt(FIELD_MAX_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate); + minVideoWidth = bundle.getInt(FIELD_MIN_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.minVideoWidth); minVideoHeight = - bundle.getInt( - keyForField(FIELD_MIN_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.minVideoHeight); + bundle.getInt(FIELD_MIN_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.minVideoHeight); minVideoFrameRate = - bundle.getInt( - keyForField(FIELD_MIN_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate); + bundle.getInt(FIELD_MIN_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate); minVideoBitrate = - bundle.getInt( - keyForField(FIELD_MIN_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.minVideoBitrate); - viewportWidth = - bundle.getInt(keyForField(FIELD_VIEWPORT_WIDTH), DEFAULT_WITHOUT_CONTEXT.viewportWidth); - viewportHeight = - bundle.getInt(keyForField(FIELD_VIEWPORT_HEIGHT), DEFAULT_WITHOUT_CONTEXT.viewportHeight); + bundle.getInt(FIELD_MIN_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.minVideoBitrate); + viewportWidth = bundle.getInt(FIELD_VIEWPORT_WIDTH, DEFAULT_WITHOUT_CONTEXT.viewportWidth); + viewportHeight = bundle.getInt(FIELD_VIEWPORT_HEIGHT, DEFAULT_WITHOUT_CONTEXT.viewportHeight); viewportOrientationMayChange = bundle.getBoolean( - keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE), + FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange); preferredVideoMimeTypes = ImmutableList.copyOf( - firstNonNull( - bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)), - new String[0])); + firstNonNull(bundle.getStringArray(FIELD_PREFERRED_VIDEO_MIMETYPES), new String[0])); preferredVideoRoleFlags = bundle.getInt( - keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), - DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags); + FIELD_PREFERRED_VIDEO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags); // Audio String[] preferredAudioLanguages1 = - firstNonNull( - bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES)), new String[0]); + firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_LANGUAGES), new String[0]); preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1); preferredAudioRoleFlags = bundle.getInt( - keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS), - DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags); + FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags); maxAudioChannelCount = bundle.getInt( - keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT), - DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount); + FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount); maxAudioBitrate = - bundle.getInt( - keyForField(FIELD_MAX_AUDIO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate); + bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate); preferredAudioMimeTypes = ImmutableList.copyOf( - firstNonNull( - bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES)), - new String[0])); + firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_MIME_TYPES), new String[0])); // Text preferredTextLanguages = normalizeLanguageCodes( - firstNonNull( - bundle.getStringArray(keyForField(FIELD_PREFERRED_TEXT_LANGUAGES)), - new String[0])); + firstNonNull(bundle.getStringArray(FIELD_PREFERRED_TEXT_LANGUAGES), new String[0])); preferredTextRoleFlags = bundle.getInt( - keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), - DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags); + FIELD_PREFERRED_TEXT_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags); ignoredTextSelectionFlags = bundle.getInt( - keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), + FIELD_IGNORED_TEXT_SELECTION_FLAGS, DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags); selectUndeterminedTextLanguage = bundle.getBoolean( - keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), + FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage); // General forceLowestBitrate = - bundle.getBoolean( - keyForField(FIELD_FORCE_LOWEST_BITRATE), DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate); + bundle.getBoolean(FIELD_FORCE_LOWEST_BITRATE, DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate); forceHighestSupportedBitrate = bundle.getBoolean( - keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), + FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); @Nullable - List overrideBundleList = - bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES)); + List overrideBundleList = bundle.getParcelableArrayList(FIELD_SELECTION_OVERRIDES); List overrideList = overrideBundleList == null ? ImmutableList.of() @@ -257,7 +233,7 @@ public class TrackSelectionParameters implements Bundleable { overrides.put(override.mediaTrackGroup, override); } int[] disabledTrackTypeArray = - firstNonNull(bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]); + firstNonNull(bundle.getIntArray(FIELD_DISABLED_TRACK_TYPE), new int[0]); disabledTrackTypes = new HashSet<>(); for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) { disabledTrackTypes.add(disabledTrackType); @@ -1098,39 +1074,40 @@ public class TrackSelectionParameters implements Bundleable { // Bundleable implementation - private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1; - private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2; - private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3; - private static final int FIELD_PREFERRED_TEXT_ROLE_FLAGS = 4; - private static final int FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = 5; - private static final int FIELD_MAX_VIDEO_WIDTH = 6; - private static final int FIELD_MAX_VIDEO_HEIGHT = 7; - private static final int FIELD_MAX_VIDEO_FRAMERATE = 8; - private static final int FIELD_MAX_VIDEO_BITRATE = 9; - private static final int FIELD_MIN_VIDEO_WIDTH = 10; - private static final int FIELD_MIN_VIDEO_HEIGHT = 11; - private static final int FIELD_MIN_VIDEO_FRAMERATE = 12; - private static final int FIELD_MIN_VIDEO_BITRATE = 13; - private static final int FIELD_VIEWPORT_WIDTH = 14; - private static final int FIELD_VIEWPORT_HEIGHT = 15; - private static final int FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = 16; - private static final int FIELD_PREFERRED_VIDEO_MIMETYPES = 17; - private static final int FIELD_MAX_AUDIO_CHANNEL_COUNT = 18; - private static final int FIELD_MAX_AUDIO_BITRATE = 19; - private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20; - private static final int FIELD_FORCE_LOWEST_BITRATE = 21; - private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22; - private static final int FIELD_SELECTION_OVERRIDES = 23; - private static final int FIELD_DISABLED_TRACK_TYPE = 24; - private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25; - private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26; + private static final String FIELD_PREFERRED_AUDIO_LANGUAGES = Util.intToStringMaxRadix(1); + private static final String FIELD_PREFERRED_AUDIO_ROLE_FLAGS = Util.intToStringMaxRadix(2); + private static final String FIELD_PREFERRED_TEXT_LANGUAGES = Util.intToStringMaxRadix(3); + private static final String FIELD_PREFERRED_TEXT_ROLE_FLAGS = Util.intToStringMaxRadix(4); + private static final String FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = Util.intToStringMaxRadix(5); + private static final String FIELD_MAX_VIDEO_WIDTH = Util.intToStringMaxRadix(6); + private static final String FIELD_MAX_VIDEO_HEIGHT = Util.intToStringMaxRadix(7); + private static final String FIELD_MAX_VIDEO_FRAMERATE = Util.intToStringMaxRadix(8); + private static final String FIELD_MAX_VIDEO_BITRATE = Util.intToStringMaxRadix(9); + private static final String FIELD_MIN_VIDEO_WIDTH = Util.intToStringMaxRadix(10); + private static final String FIELD_MIN_VIDEO_HEIGHT = Util.intToStringMaxRadix(11); + private static final String FIELD_MIN_VIDEO_FRAMERATE = Util.intToStringMaxRadix(12); + private static final String FIELD_MIN_VIDEO_BITRATE = Util.intToStringMaxRadix(13); + private static final String FIELD_VIEWPORT_WIDTH = Util.intToStringMaxRadix(14); + private static final String FIELD_VIEWPORT_HEIGHT = Util.intToStringMaxRadix(15); + private static final String FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = Util.intToStringMaxRadix(16); + private static final String FIELD_PREFERRED_VIDEO_MIMETYPES = Util.intToStringMaxRadix(17); + private static final String FIELD_MAX_AUDIO_CHANNEL_COUNT = Util.intToStringMaxRadix(18); + private static final String FIELD_MAX_AUDIO_BITRATE = Util.intToStringMaxRadix(19); + private static final String FIELD_PREFERRED_AUDIO_MIME_TYPES = Util.intToStringMaxRadix(20); + private static final String FIELD_FORCE_LOWEST_BITRATE = Util.intToStringMaxRadix(21); + private static final String FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = Util.intToStringMaxRadix(22); + private static final String FIELD_SELECTION_OVERRIDES = Util.intToStringMaxRadix(23); + private static final String FIELD_DISABLED_TRACK_TYPE = Util.intToStringMaxRadix(24); + private static final String FIELD_PREFERRED_VIDEO_ROLE_FLAGS = Util.intToStringMaxRadix(25); + private static final String FIELD_IGNORED_TEXT_SELECTION_FLAGS = Util.intToStringMaxRadix(26); /** * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()} * and {@link Bundleable.Creator}. * *

    Subclasses should obtain keys for their {@link Bundle} representation by applying a - * non-negative offset on this constant and passing the result to {@link #keyForField(int)}. + * non-negative offset on this constant and passing the result to {@link + * Util#intToStringMaxRadix(int)}. */ protected static final int FIELD_CUSTOM_ID_BASE = 1000; @@ -1139,46 +1116,39 @@ public class TrackSelectionParameters implements Bundleable { Bundle bundle = new Bundle(); // Video - bundle.putInt(keyForField(FIELD_MAX_VIDEO_WIDTH), maxVideoWidth); - bundle.putInt(keyForField(FIELD_MAX_VIDEO_HEIGHT), maxVideoHeight); - bundle.putInt(keyForField(FIELD_MAX_VIDEO_FRAMERATE), maxVideoFrameRate); - bundle.putInt(keyForField(FIELD_MAX_VIDEO_BITRATE), maxVideoBitrate); - bundle.putInt(keyForField(FIELD_MIN_VIDEO_WIDTH), minVideoWidth); - bundle.putInt(keyForField(FIELD_MIN_VIDEO_HEIGHT), minVideoHeight); - bundle.putInt(keyForField(FIELD_MIN_VIDEO_FRAMERATE), minVideoFrameRate); - bundle.putInt(keyForField(FIELD_MIN_VIDEO_BITRATE), minVideoBitrate); - bundle.putInt(keyForField(FIELD_VIEWPORT_WIDTH), viewportWidth); - bundle.putInt(keyForField(FIELD_VIEWPORT_HEIGHT), viewportHeight); - bundle.putBoolean( - keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE), viewportOrientationMayChange); + bundle.putInt(FIELD_MAX_VIDEO_WIDTH, maxVideoWidth); + bundle.putInt(FIELD_MAX_VIDEO_HEIGHT, maxVideoHeight); + bundle.putInt(FIELD_MAX_VIDEO_FRAMERATE, maxVideoFrameRate); + bundle.putInt(FIELD_MAX_VIDEO_BITRATE, maxVideoBitrate); + bundle.putInt(FIELD_MIN_VIDEO_WIDTH, minVideoWidth); + bundle.putInt(FIELD_MIN_VIDEO_HEIGHT, minVideoHeight); + bundle.putInt(FIELD_MIN_VIDEO_FRAMERATE, minVideoFrameRate); + bundle.putInt(FIELD_MIN_VIDEO_BITRATE, minVideoBitrate); + bundle.putInt(FIELD_VIEWPORT_WIDTH, viewportWidth); + bundle.putInt(FIELD_VIEWPORT_HEIGHT, viewportHeight); + bundle.putBoolean(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, viewportOrientationMayChange); bundle.putStringArray( - keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES), - preferredVideoMimeTypes.toArray(new String[0])); - bundle.putInt(keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), preferredVideoRoleFlags); + FIELD_PREFERRED_VIDEO_MIMETYPES, preferredVideoMimeTypes.toArray(new String[0])); + bundle.putInt(FIELD_PREFERRED_VIDEO_ROLE_FLAGS, preferredVideoRoleFlags); // Audio bundle.putStringArray( - keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES), - preferredAudioLanguages.toArray(new String[0])); - bundle.putInt(keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS), preferredAudioRoleFlags); - bundle.putInt(keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT), maxAudioChannelCount); - bundle.putInt(keyForField(FIELD_MAX_AUDIO_BITRATE), maxAudioBitrate); + FIELD_PREFERRED_AUDIO_LANGUAGES, preferredAudioLanguages.toArray(new String[0])); + bundle.putInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, preferredAudioRoleFlags); + bundle.putInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, maxAudioChannelCount); + bundle.putInt(FIELD_MAX_AUDIO_BITRATE, maxAudioBitrate); bundle.putStringArray( - keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES), - preferredAudioMimeTypes.toArray(new String[0])); + FIELD_PREFERRED_AUDIO_MIME_TYPES, preferredAudioMimeTypes.toArray(new String[0])); // Text bundle.putStringArray( - keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0])); - bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags); - bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags); - bundle.putBoolean( - keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage); + FIELD_PREFERRED_TEXT_LANGUAGES, preferredTextLanguages.toArray(new String[0])); + bundle.putInt(FIELD_PREFERRED_TEXT_ROLE_FLAGS, preferredTextRoleFlags); + bundle.putInt(FIELD_IGNORED_TEXT_SELECTION_FLAGS, ignoredTextSelectionFlags); + bundle.putBoolean(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, selectUndeterminedTextLanguage); // General - bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate); - bundle.putBoolean( - keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate); - bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values())); - bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes)); + bundle.putBoolean(FIELD_FORCE_LOWEST_BITRATE, forceLowestBitrate); + bundle.putBoolean(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, forceHighestSupportedBitrate); + bundle.putParcelableArrayList(FIELD_SELECTION_OVERRIDES, toBundleArrayList(overrides.values())); + bundle.putIntArray(FIELD_DISABLED_TRACK_TYPE, Ints.toArray(disabledTrackTypes)); return bundle; } @@ -1194,15 +1164,4 @@ public class TrackSelectionParameters implements Bundleable { @Deprecated public static final Creator CREATOR = TrackSelectionParameters::fromBundle; - - /** - * Converts the given field number to a string which can be used as a field key when implementing - * {@link #toBundle()} and {@link Bundleable.Creator}. - * - *

    Subclasses should use {@code field} values greater than or equal to {@link - * #FIELD_CUSTOM_ID_BASE}. - */ - protected static String keyForField(int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 017cb525b4..ed042272a2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -2763,6 +2763,15 @@ public final class Util { : resources.getDrawable(drawableRes); } + /** + * Returns a string representation of the integer using radix value {@link Character#MAX_RADIX}. + * + * @param i An integer to be converted to String. + */ + public static String intToStringMaxRadix(int i) { + return Integer.toString(i, Character.MAX_RADIX); + } + @Nullable private static String getSystemProperty(String name) { try { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java index 9215907a39..53a0a4147a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java @@ -15,18 +15,12 @@ */ package com.google.android.exoplayer2.video; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; 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; -import java.lang.annotation.Target; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import org.checkerframework.dataflow.qual.Pure; @@ -184,41 +178,26 @@ public final class ColorInfo implements Bundleable { // Bundleable implementation - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_COLOR_SPACE, - FIELD_COLOR_RANGE, - FIELD_COLOR_TRANSFER, - FIELD_HDR_STATIC_INFO, - }) - private @interface FieldNumber {} - - private static final int FIELD_COLOR_SPACE = 0; - private static final int FIELD_COLOR_RANGE = 1; - private static final int FIELD_COLOR_TRANSFER = 2; - private static final int FIELD_HDR_STATIC_INFO = 3; + private static final String FIELD_COLOR_SPACE = Util.intToStringMaxRadix(0); + private static final String FIELD_COLOR_RANGE = Util.intToStringMaxRadix(1); + private static final String FIELD_COLOR_TRANSFER = Util.intToStringMaxRadix(2); + private static final String FIELD_HDR_STATIC_INFO = Util.intToStringMaxRadix(3); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_COLOR_SPACE), colorSpace); - bundle.putInt(keyForField(FIELD_COLOR_RANGE), colorRange); - bundle.putInt(keyForField(FIELD_COLOR_TRANSFER), colorTransfer); - bundle.putByteArray(keyForField(FIELD_HDR_STATIC_INFO), hdrStaticInfo); + bundle.putInt(FIELD_COLOR_SPACE, colorSpace); + bundle.putInt(FIELD_COLOR_RANGE, colorRange); + bundle.putInt(FIELD_COLOR_TRANSFER, colorTransfer); + bundle.putByteArray(FIELD_HDR_STATIC_INFO, hdrStaticInfo); return bundle; } public static final Creator CREATOR = bundle -> new ColorInfo( - bundle.getInt(keyForField(FIELD_COLOR_SPACE), Format.NO_VALUE), - bundle.getInt(keyForField(FIELD_COLOR_RANGE), Format.NO_VALUE), - bundle.getInt(keyForField(FIELD_COLOR_TRANSFER), Format.NO_VALUE), - bundle.getByteArray(keyForField(FIELD_HDR_STATIC_INFO))); - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } + bundle.getInt(FIELD_COLOR_SPACE, Format.NO_VALUE), + bundle.getInt(FIELD_COLOR_RANGE, Format.NO_VALUE), + bundle.getInt(FIELD_COLOR_TRANSFER, Format.NO_VALUE), + bundle.getByteArray(FIELD_HDR_STATIC_INFO)); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java index 2ae09d9ee5..2a9a1861d0 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java @@ -15,18 +15,12 @@ */ package com.google.android.exoplayer2.video; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; import androidx.annotation.FloatRange; -import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import com.google.android.exoplayer2.util.Util; /** Represents the video size. */ public final class VideoSize implements Bundleable { @@ -130,46 +124,30 @@ public final class VideoSize implements Bundleable { } // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_WIDTH, - FIELD_HEIGHT, - FIELD_UNAPPLIED_ROTATION_DEGREES, - FIELD_PIXEL_WIDTH_HEIGHT_RATIO, - }) - private @interface FieldNumber {} - private static final int FIELD_WIDTH = 0; - private static final int FIELD_HEIGHT = 1; - private static final int FIELD_UNAPPLIED_ROTATION_DEGREES = 2; - private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 3; + private static final String FIELD_WIDTH = Util.intToStringMaxRadix(0); + private static final String FIELD_HEIGHT = Util.intToStringMaxRadix(1); + private static final String FIELD_UNAPPLIED_ROTATION_DEGREES = Util.intToStringMaxRadix(2); + private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(3); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_WIDTH), width); - bundle.putInt(keyForField(FIELD_HEIGHT), height); - bundle.putInt(keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), unappliedRotationDegrees); - bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); + bundle.putInt(FIELD_WIDTH, width); + bundle.putInt(FIELD_HEIGHT, height); + bundle.putInt(FIELD_UNAPPLIED_ROTATION_DEGREES, unappliedRotationDegrees); + bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio); return bundle; } public static final Creator CREATOR = bundle -> { - int width = bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT_WIDTH); - int height = bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT_HEIGHT); + int width = bundle.getInt(FIELD_WIDTH, DEFAULT_WIDTH); + int height = bundle.getInt(FIELD_HEIGHT, DEFAULT_HEIGHT); int unappliedRotationDegrees = - bundle.getInt( - keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), DEFAULT_UNAPPLIED_ROTATION_DEGREES); + bundle.getInt(FIELD_UNAPPLIED_ROTATION_DEGREES, DEFAULT_UNAPPLIED_ROTATION_DEGREES); float pixelWidthHeightRatio = - bundle.getFloat( - keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); + bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index 45f91012c9..18939337de 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -861,6 +861,8 @@ public class MediaItemTest { @Test public void createMediaItemInstance_roundTripViaBundle_yieldsEqualInstance() { + Bundle extras = new Bundle(); + extras.putString("key", "value"); // Creates instance by setting some non-default values MediaItem mediaItem = new MediaItem.Builder() @@ -876,11 +878,14 @@ public class MediaItemTest { new RequestMetadata.Builder() .setMediaUri(Uri.parse("http://test.test")) .setSearchQuery("search") + .setExtras(extras) .build()) .build(); MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle()); assertThat(mediaItemFromBundle).isEqualTo(mediaItem); + assertThat(mediaItemFromBundle.requestMetadata.extras) + .isEqualTo(mediaItem.requestMetadata.extras); } } 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 05473f25cc..1fd94479b8 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 @@ -240,17 +240,15 @@ public final class ExoPlaybackException extends PlaybackException { private ExoPlaybackException(Bundle bundle) { super(bundle); - type = bundle.getInt(keyForField(FIELD_TYPE), /* defaultValue= */ TYPE_UNEXPECTED); - rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME)); - rendererIndex = - bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET); - @Nullable Bundle rendererFormatBundle = bundle.getBundle(keyForField(FIELD_RENDERER_FORMAT)); + type = bundle.getInt(FIELD_TYPE, /* defaultValue= */ TYPE_UNEXPECTED); + rendererName = bundle.getString(FIELD_RENDERER_NAME); + rendererIndex = bundle.getInt(FIELD_RENDERER_INDEX, /* defaultValue= */ C.INDEX_UNSET); + @Nullable Bundle rendererFormatBundle = bundle.getBundle(FIELD_RENDERER_FORMAT); rendererFormat = rendererFormatBundle == null ? null : Format.CREATOR.fromBundle(rendererFormatBundle); rendererFormatSupport = - bundle.getInt( - keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED); - isRecoverable = bundle.getBoolean(keyForField(FIELD_IS_RECOVERABLE), /* defaultValue= */ false); + bundle.getInt(FIELD_RENDERER_FORMAT_SUPPORT, /* defaultValue= */ C.FORMAT_HANDLED); + isRecoverable = bundle.getBoolean(FIELD_IS_RECOVERABLE, /* defaultValue= */ false); mediaPeriodId = null; } @@ -389,12 +387,17 @@ public final class ExoPlaybackException extends PlaybackException { /** Object that can restore {@link ExoPlaybackException} from a {@link Bundle}. */ public static final Creator CREATOR = ExoPlaybackException::new; - private static final int FIELD_TYPE = FIELD_CUSTOM_ID_BASE + 1; - private static final int FIELD_RENDERER_NAME = FIELD_CUSTOM_ID_BASE + 2; - private static final int FIELD_RENDERER_INDEX = FIELD_CUSTOM_ID_BASE + 3; - private static final int FIELD_RENDERER_FORMAT = FIELD_CUSTOM_ID_BASE + 4; - private static final int FIELD_RENDERER_FORMAT_SUPPORT = FIELD_CUSTOM_ID_BASE + 5; - private static final int FIELD_IS_RECOVERABLE = FIELD_CUSTOM_ID_BASE + 6; + private static final String FIELD_TYPE = Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 1); + private static final String FIELD_RENDERER_NAME = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 2); + private static final String FIELD_RENDERER_INDEX = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 3); + private static final String FIELD_RENDERER_FORMAT = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 4); + private static final String FIELD_RENDERER_FORMAT_SUPPORT = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 5); + private static final String FIELD_IS_RECOVERABLE = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 6); /** * {@inheritDoc} @@ -405,14 +408,14 @@ public final class ExoPlaybackException extends PlaybackException { @Override public Bundle toBundle() { Bundle bundle = super.toBundle(); - bundle.putInt(keyForField(FIELD_TYPE), type); - bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName); - bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex); + bundle.putInt(FIELD_TYPE, type); + bundle.putString(FIELD_RENDERER_NAME, rendererName); + bundle.putInt(FIELD_RENDERER_INDEX, rendererIndex); if (rendererFormat != null) { - bundle.putBundle(keyForField(FIELD_RENDERER_FORMAT), rendererFormat.toBundle()); + bundle.putBundle(FIELD_RENDERER_FORMAT, rendererFormat.toBundle()); } - bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport); - bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable); + bundle.putInt(FIELD_RENDERER_FORMAT_SUPPORT, rendererFormatSupport); + bundle.putBoolean(FIELD_IS_RECOVERABLE, isRecoverable); return bundle; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java index d88781e0bc..66917a3ae0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java @@ -15,20 +15,14 @@ */ package com.google.android.exoplayer2.source; -import static java.lang.annotation.ElementType.TYPE_USE; - import android.os.Bundle; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.BundleableUtil; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.List; /** @@ -115,21 +109,13 @@ public final class TrackGroupArray implements Bundleable { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_TRACK_GROUPS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TRACK_GROUPS = 0; + private static final String FIELD_TRACK_GROUPS = Util.intToStringMaxRadix(0); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); bundle.putParcelableArrayList( - keyForField(FIELD_TRACK_GROUPS), BundleableUtil.toBundleArrayList(trackGroups)); + FIELD_TRACK_GROUPS, BundleableUtil.toBundleArrayList(trackGroups)); return bundle; } @@ -137,8 +123,7 @@ public final class TrackGroupArray implements Bundleable { public static final Creator CREATOR = bundle -> { @Nullable - List trackGroupBundles = - bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS)); + List trackGroupBundles = bundle.getParcelableArrayList(FIELD_TRACK_GROUPS); if (trackGroupBundles == null) { return new TrackGroupArray(); } @@ -160,8 +145,4 @@ public final class TrackGroupArray implements Bundleable { } } } - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } 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 48c117194d..786f789857 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 @@ -823,69 +823,62 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Video setExceedVideoConstraintsIfNecessary( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY), + Parameters.FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY, defaultValue.exceedVideoConstraintsIfNecessary)); setAllowVideoMixedMimeTypeAdaptiveness( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS), + Parameters.FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS, defaultValue.allowVideoMixedMimeTypeAdaptiveness)); setAllowVideoNonSeamlessAdaptiveness( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), + Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS, defaultValue.allowVideoNonSeamlessAdaptiveness)); setAllowVideoMixedDecoderSupportAdaptiveness( bundle.getBoolean( - Parameters.keyForField( - Parameters.FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + Parameters.FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS, defaultValue.allowVideoMixedDecoderSupportAdaptiveness)); // Audio setExceedAudioConstraintsIfNecessary( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY), + Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY, defaultValue.exceedAudioConstraintsIfNecessary)); setAllowAudioMixedMimeTypeAdaptiveness( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS), + Parameters.FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS, defaultValue.allowAudioMixedMimeTypeAdaptiveness)); setAllowAudioMixedSampleRateAdaptiveness( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS), + Parameters.FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS, defaultValue.allowAudioMixedSampleRateAdaptiveness)); setAllowAudioMixedChannelCountAdaptiveness( bundle.getBoolean( - Parameters.keyForField( - Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), + Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS, defaultValue.allowAudioMixedChannelCountAdaptiveness)); setAllowAudioMixedDecoderSupportAdaptiveness( bundle.getBoolean( - Parameters.keyForField( - Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS, defaultValue.allowAudioMixedDecoderSupportAdaptiveness)); setConstrainAudioChannelCountToDeviceCapabilities( bundle.getBoolean( - Parameters.keyForField( - Parameters.FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES), + Parameters.FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES, defaultValue.constrainAudioChannelCountToDeviceCapabilities)); // General setExceedRendererCapabilitiesIfNecessary( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY), + Parameters.FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY, defaultValue.exceedRendererCapabilitiesIfNecessary)); setTunnelingEnabled( - bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_TUNNELING_ENABLED), - defaultValue.tunnelingEnabled)); + bundle.getBoolean(Parameters.FIELD_TUNNELING_ENABLED, defaultValue.tunnelingEnabled)); setAllowMultipleAdaptiveSelections( bundle.getBoolean( - Parameters.keyForField(Parameters.FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS), + Parameters.FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS, defaultValue.allowMultipleAdaptiveSelections)); // Overrides selectionOverrides = new SparseArray<>(); setSelectionOverridesFromBundle(bundle); rendererDisabledFlags = makeSparseBooleanArrayFromTrueKeys( - bundle.getIntArray( - Parameters.keyForField(Parameters.FIELD_RENDERER_DISABLED_INDICES))); + bundle.getIntArray(Parameters.FIELD_RENDERER_DISABLED_INDICES)); } @CanIgnoreReturnValue @@ -1567,20 +1560,17 @@ public class DefaultTrackSelector extends MappingTrackSelector { private void setSelectionOverridesFromBundle(Bundle bundle) { @Nullable int[] rendererIndices = - bundle.getIntArray( - Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_RENDERER_INDICES)); + bundle.getIntArray(Parameters.FIELD_SELECTION_OVERRIDES_RENDERER_INDICES); @Nullable ArrayList trackGroupArrayBundles = - bundle.getParcelableArrayList( - Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS)); + bundle.getParcelableArrayList(Parameters.FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS); List trackGroupArrays = trackGroupArrayBundles == null ? ImmutableList.of() : BundleableUtil.fromBundleList(TrackGroupArray.CREATOR, trackGroupArrayBundles); @Nullable SparseArray selectionOverrideBundles = - bundle.getSparseParcelableArray( - Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES)); + bundle.getSparseParcelableArray(Parameters.FIELD_SELECTION_OVERRIDES); SparseArray selectionOverrides = selectionOverrideBundles == null ? new SparseArray<>() @@ -1870,32 +1860,40 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Bundleable implementation. - private static final int FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY = FIELD_CUSTOM_ID_BASE; - private static final int FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 1; - private static final int FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS = FIELD_CUSTOM_ID_BASE + 2; - private static final int FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY = FIELD_CUSTOM_ID_BASE + 3; - private static final int FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 4; - private static final int FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 5; - private static final int FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 6; - private static final int FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY = - FIELD_CUSTOM_ID_BASE + 7; - private static final int FIELD_TUNNELING_ENABLED = FIELD_CUSTOM_ID_BASE + 8; - private static final int FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS = FIELD_CUSTOM_ID_BASE + 9; - private static final int FIELD_SELECTION_OVERRIDES_RENDERER_INDICES = FIELD_CUSTOM_ID_BASE + 10; - private static final int FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS = - FIELD_CUSTOM_ID_BASE + 11; - private static final int FIELD_SELECTION_OVERRIDES = FIELD_CUSTOM_ID_BASE + 12; - private static final int FIELD_RENDERER_DISABLED_INDICES = FIELD_CUSTOM_ID_BASE + 13; - private static final int FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 14; - private static final int FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = - FIELD_CUSTOM_ID_BASE + 15; - private static final int FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES = - FIELD_CUSTOM_ID_BASE + 16; + private static final String FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE); + private static final String FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 1); + private static final String FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 2); + private static final String FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 3); + private static final String FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 4); + private static final String FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 5); + private static final String FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 6); + private static final String FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 7); + private static final String FIELD_TUNNELING_ENABLED = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 8); + private static final String FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 9); + private static final String FIELD_SELECTION_OVERRIDES_RENDERER_INDICES = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 10); + private static final String FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 11); + private static final String FIELD_SELECTION_OVERRIDES = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 12); + private static final String FIELD_RENDERER_DISABLED_INDICES = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 13); + private static final String FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 14); + private static final String FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 15); + private static final String FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES = + Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 16); @Override public Bundle toBundle() { @@ -1903,49 +1901,40 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Video bundle.putBoolean( - keyForField(FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY), - exceedVideoConstraintsIfNecessary); + FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY, exceedVideoConstraintsIfNecessary); bundle.putBoolean( - keyForField(FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS), - allowVideoMixedMimeTypeAdaptiveness); + FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS, allowVideoMixedMimeTypeAdaptiveness); bundle.putBoolean( - keyForField(FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), - allowVideoNonSeamlessAdaptiveness); + FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS, allowVideoNonSeamlessAdaptiveness); bundle.putBoolean( - keyForField(FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS, allowVideoMixedDecoderSupportAdaptiveness); // Audio bundle.putBoolean( - keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY), - exceedAudioConstraintsIfNecessary); + FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY, exceedAudioConstraintsIfNecessary); bundle.putBoolean( - keyForField(FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS), - allowAudioMixedMimeTypeAdaptiveness); + FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS, allowAudioMixedMimeTypeAdaptiveness); bundle.putBoolean( - keyForField(FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS), - allowAudioMixedSampleRateAdaptiveness); + FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS, allowAudioMixedSampleRateAdaptiveness); bundle.putBoolean( - keyForField(FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), + FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS, allowAudioMixedChannelCountAdaptiveness); bundle.putBoolean( - keyForField(FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS, allowAudioMixedDecoderSupportAdaptiveness); bundle.putBoolean( - keyForField(FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES), + FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES, constrainAudioChannelCountToDeviceCapabilities); // General bundle.putBoolean( - keyForField(FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY), - exceedRendererCapabilitiesIfNecessary); - bundle.putBoolean(keyForField(FIELD_TUNNELING_ENABLED), tunnelingEnabled); - bundle.putBoolean( - keyForField(FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS), allowMultipleAdaptiveSelections); + FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY, exceedRendererCapabilitiesIfNecessary); + bundle.putBoolean(FIELD_TUNNELING_ENABLED, tunnelingEnabled); + bundle.putBoolean(FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS, allowMultipleAdaptiveSelections); putSelectionOverridesToBundle(bundle, selectionOverrides); // Only true values are put into rendererDisabledFlags. bundle.putIntArray( - keyForField(FIELD_RENDERER_DISABLED_INDICES), - getKeysFromSparseBooleanArray(rendererDisabledFlags)); + FIELD_RENDERER_DISABLED_INDICES, getKeysFromSparseBooleanArray(rendererDisabledFlags)); return bundle; } @@ -1978,12 +1967,12 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererIndices.add(rendererIndex); } bundle.putIntArray( - keyForField(FIELD_SELECTION_OVERRIDES_RENDERER_INDICES), Ints.toArray(rendererIndices)); + FIELD_SELECTION_OVERRIDES_RENDERER_INDICES, Ints.toArray(rendererIndices)); bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS), + FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS, BundleableUtil.toBundleArrayList(trackGroupArrays)); bundle.putSparseParcelableArray( - keyForField(FIELD_SELECTION_OVERRIDES), BundleableUtil.toBundleSparseArray(selections)); + FIELD_SELECTION_OVERRIDES, BundleableUtil.toBundleSparseArray(selections)); } } @@ -2111,43 +2100,29 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Bundleable implementation. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - FIELD_GROUP_INDEX, - FIELD_TRACKS, - FIELD_TRACK_TYPE, - }) - private @interface FieldNumber {} - - private static final int FIELD_GROUP_INDEX = 0; - private static final int FIELD_TRACKS = 1; - private static final int FIELD_TRACK_TYPE = 2; + private static final String FIELD_GROUP_INDEX = Util.intToStringMaxRadix(0); + private static final String FIELD_TRACKS = Util.intToStringMaxRadix(1); + private static final String FIELD_TRACK_TYPE = Util.intToStringMaxRadix(2); @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putInt(keyForField(FIELD_GROUP_INDEX), groupIndex); - bundle.putIntArray(keyForField(FIELD_TRACKS), tracks); - bundle.putInt(keyForField(FIELD_TRACK_TYPE), type); + bundle.putInt(FIELD_GROUP_INDEX, groupIndex); + bundle.putIntArray(FIELD_TRACKS, tracks); + bundle.putInt(FIELD_TRACK_TYPE, type); return bundle; } /** Object that can restore {@code SelectionOverride} from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> { - int groupIndex = bundle.getInt(keyForField(FIELD_GROUP_INDEX), -1); - @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); - int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), -1); + int groupIndex = bundle.getInt(FIELD_GROUP_INDEX, -1); + @Nullable int[] tracks = bundle.getIntArray(FIELD_TRACKS); + int trackType = bundle.getInt(FIELD_TRACK_TYPE, -1); Assertions.checkArgument(groupIndex >= 0 && trackType >= 0); Assertions.checkNotNull(tracks); return new SelectionOverride(groupIndex, tracks, trackType); }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** From f4386071d75ade26bf9b20ddec98ab5cdf574ec4 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 10 Jan 2023 17:08:34 +0000 Subject: [PATCH 077/104] Update bandwidth meter estimates PiperOrigin-RevId: 501010994 (cherry picked from commit 09a15fb73103c406965856de4d120a5028bb37b5) --- .../upstream/DefaultBandwidthMeter.java | 470 +++++++++--------- 1 file changed, 243 insertions(+), 227 deletions(-) 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 df6060077c..7803f316e2 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 @@ -43,27 +43,27 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** Default initial Wifi bitrate estimate in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = - ImmutableList.of(4_800_000L, 3_100_000L, 2_100_000L, 1_500_000L, 800_000L); + ImmutableList.of(4_400_000L, 3_200_000L, 2_300_000L, 1_600_000L, 810_000L); /** Default initial 2G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = - ImmutableList.of(1_500_000L, 1_000_000L, 730_000L, 440_000L, 170_000L); + ImmutableList.of(1_400_000L, 990_000L, 730_000L, 510_000L, 230_000L); /** Default initial 3G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = - ImmutableList.of(2_200_000L, 1_400_000L, 1_100_000L, 910_000L, 620_000L); + ImmutableList.of(2_100_000L, 1_400_000L, 1_000_000L, 890_000L, 640_000L); /** Default initial 4G bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = - ImmutableList.of(3_000_000L, 1_900_000L, 1_400_000L, 1_000_000L, 660_000L); + ImmutableList.of(2_600_000L, 1_700_000L, 1_300_000L, 1_000_000L, 700_000L); /** Default initial 5G-NSA bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA = - ImmutableList.of(6_000_000L, 4_100_000L, 3_200_000L, 1_800_000L, 1_000_000L); + ImmutableList.of(5_700_000L, 3_700_000L, 2_300_000L, 1_700_000L, 990_000L); /** Default initial 5G-SA bitrate estimates in bits per second. */ public static final ImmutableList DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA = - ImmutableList.of(2_800_000L, 2_400_000L, 1_600_000L, 1_100_000L, 950_000L); + ImmutableList.of(2_800_000L, 1_800_000L, 1_400_000L, 1_100_000L, 870_000L); /** * Default initial bitrate estimate used when the device is offline or the network type cannot be @@ -478,394 +478,410 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList */ private static int[] getInitialBitrateCountryGroupAssignment(String country) { switch (country) { + case "AD": + case "CW": + return new int[] {2, 2, 0, 0, 2, 2}; case "AE": - return new int[] {1, 4, 4, 4, 4, 0}; + return new int[] {1, 4, 3, 4, 4, 2}; case "AG": - return new int[] {2, 4, 1, 2, 2, 2}; - case "AI": - return new int[] {0, 2, 0, 3, 2, 2}; + return new int[] {2, 4, 3, 4, 2, 2}; + case "AL": + return new int[] {1, 1, 1, 3, 2, 2}; case "AM": return new int[] {2, 3, 2, 3, 2, 2}; case "AO": - return new int[] {4, 4, 3, 2, 2, 2}; + return new int[] {4, 4, 4, 3, 2, 2}; case "AS": return new int[] {2, 2, 3, 3, 2, 2}; case "AT": - return new int[] {1, 0, 1, 1, 0, 0}; + return new int[] {1, 2, 1, 4, 1, 4}; case "AU": - return new int[] {0, 1, 1, 1, 2, 0}; - case "AW": - return new int[] {1, 3, 4, 4, 2, 2}; - case "BA": - return new int[] {1, 2, 1, 1, 2, 2}; - case "BD": - return new int[] {2, 1, 3, 3, 2, 2}; + return new int[] {0, 2, 1, 1, 3, 0}; case "BE": return new int[] {0, 1, 4, 4, 3, 2}; - case "BF": - return new int[] {4, 3, 4, 3, 2, 2}; case "BH": - return new int[] {1, 2, 1, 3, 4, 2}; + return new int[] {1, 3, 1, 4, 4, 2}; case "BJ": - return new int[] {4, 4, 3, 3, 2, 2}; + return new int[] {4, 4, 2, 3, 2, 2}; + case "BN": + return new int[] {3, 2, 0, 1, 2, 2}; case "BO": return new int[] {1, 2, 3, 2, 2, 2}; - case "BS": - return new int[] {4, 4, 2, 2, 2, 2}; - case "BT": - return new int[] {3, 1, 3, 2, 2, 2}; + case "BR": + return new int[] {1, 1, 2, 1, 1, 0}; case "BW": return new int[] {3, 2, 1, 0, 2, 2}; case "BY": - return new int[] {0, 1, 2, 3, 2, 2}; - case "BZ": - return new int[] {2, 4, 2, 1, 2, 2}; + return new int[] {1, 1, 2, 3, 2, 2}; case "CA": - return new int[] {0, 2, 2, 2, 3, 2}; - case "CD": - return new int[] {4, 2, 3, 2, 2, 2}; + return new int[] {0, 2, 3, 3, 3, 3}; case "CH": - return new int[] {0, 0, 0, 1, 0, 2}; + return new int[] {0, 0, 0, 0, 0, 3}; + case "BZ": + case "CK": + return new int[] {2, 2, 2, 1, 2, 2}; + case "CL": + return new int[] {1, 1, 2, 1, 3, 2}; case "CM": - return new int[] {3, 3, 3, 3, 2, 2}; + return new int[] {4, 3, 3, 4, 2, 2}; case "CN": - return new int[] {2, 0, 1, 1, 3, 2}; + return new int[] {2, 0, 4, 3, 3, 1}; case "CO": - return new int[] {2, 3, 4, 3, 2, 2}; + return new int[] {2, 3, 4, 2, 2, 2}; case "CR": - return new int[] {2, 3, 4, 4, 2, 2}; + return new int[] {2, 4, 4, 4, 2, 2}; case "CV": - return new int[] {2, 1, 0, 0, 2, 2}; - case "BN": - case "CW": - return new int[] {2, 2, 0, 0, 2, 2}; + return new int[] {2, 3, 0, 1, 2, 2}; + case "CZ": + return new int[] {0, 0, 2, 0, 1, 2}; case "DE": - return new int[] {0, 1, 2, 2, 2, 3}; - case "DK": - return new int[] {0, 0, 3, 2, 0, 2}; + return new int[] {0, 1, 3, 2, 2, 2}; case "DO": return new int[] {3, 4, 4, 4, 4, 2}; + case "AZ": + case "BF": + case "DZ": + return new int[] {3, 3, 4, 4, 2, 2}; case "EC": - return new int[] {2, 3, 2, 1, 2, 2}; - case "ET": - return new int[] {4, 3, 3, 1, 2, 2}; + return new int[] {1, 3, 2, 1, 2, 2}; + case "CI": + case "EG": + return new int[] {3, 4, 3, 3, 2, 2}; case "FI": - return new int[] {0, 0, 0, 3, 0, 2}; + return new int[] {0, 0, 0, 2, 0, 2}; case "FJ": - return new int[] {3, 1, 2, 2, 2, 2}; + return new int[] {3, 1, 2, 3, 2, 2}; case "FM": - return new int[] {4, 2, 4, 1, 2, 2}; - case "FR": - return new int[] {1, 2, 3, 1, 0, 2}; - case "GB": - return new int[] {0, 0, 1, 1, 1, 1}; - case "GE": - return new int[] {1, 1, 1, 2, 2, 2}; + return new int[] {4, 2, 3, 0, 2, 2}; + case "AI": case "BB": + case "BM": + case "BQ": case "DM": case "FO": - case "GI": return new int[] {0, 2, 0, 0, 2, 2}; - case "AF": - case "GM": - return new int[] {4, 3, 3, 4, 2, 2}; - case "GN": - return new int[] {4, 3, 4, 2, 2, 2}; - case "GQ": - return new int[] {4, 2, 1, 4, 2, 2}; - case "GT": - return new int[] {2, 3, 2, 2, 2, 2}; + case "FR": + return new int[] {1, 1, 2, 1, 1, 2}; + case "GB": + return new int[] {0, 1, 1, 2, 1, 2}; + case "GE": + return new int[] {1, 0, 0, 2, 2, 2}; + case "GG": + return new int[] {0, 2, 1, 0, 2, 2}; case "CG": - case "EG": + case "GH": + return new int[] {3, 3, 3, 3, 2, 2}; + case "GM": + return new int[] {4, 3, 2, 4, 2, 2}; + case "GN": + return new int[] {4, 4, 4, 2, 2, 2}; + case "GP": + return new int[] {3, 1, 1, 3, 2, 2}; + case "GQ": + return new int[] {4, 4, 3, 3, 2, 2}; + case "GT": + return new int[] {2, 2, 2, 1, 1, 2}; + case "AW": + case "GU": + return new int[] {1, 2, 4, 4, 2, 2}; case "GW": - return new int[] {3, 4, 3, 3, 2, 2}; + return new int[] {4, 4, 2, 2, 2, 2}; case "GY": - return new int[] {3, 2, 2, 1, 2, 2}; + return new int[] {3, 0, 1, 1, 2, 2}; case "HK": - return new int[] {0, 1, 2, 3, 2, 0}; - case "HU": - return new int[] {0, 0, 0, 1, 3, 2}; + return new int[] {0, 1, 1, 3, 2, 0}; + case "HN": + return new int[] {3, 3, 2, 2, 2, 2}; case "ID": - return new int[] {3, 1, 2, 2, 3, 2}; - case "ES": + return new int[] {3, 1, 1, 2, 3, 2}; + case "BA": case "IE": - return new int[] {0, 1, 1, 1, 2, 2}; - case "CL": + return new int[] {1, 1, 1, 1, 2, 2}; case "IL": - return new int[] {1, 2, 2, 2, 3, 2}; + return new int[] {1, 2, 2, 3, 4, 2}; + case "IM": + return new int[] {0, 2, 0, 1, 2, 2}; case "IN": - return new int[] {1, 1, 3, 2, 3, 3}; - case "IQ": - return new int[] {3, 2, 2, 3, 2, 2}; + return new int[] {1, 1, 2, 1, 2, 1}; case "IR": - return new int[] {3, 0, 1, 1, 4, 1}; + return new int[] {4, 2, 3, 3, 4, 2}; + case "IS": + return new int[] {0, 0, 1, 0, 0, 2}; case "IT": - return new int[] {0, 0, 0, 1, 1, 2}; + return new int[] {0, 0, 1, 1, 1, 2}; + case "GI": + case "JE": + return new int[] {1, 2, 0, 1, 2, 2}; case "JM": - return new int[] {2, 4, 3, 2, 2, 2}; + return new int[] {2, 4, 2, 1, 2, 2}; case "JO": - return new int[] {2, 1, 1, 2, 2, 2}; + return new int[] {2, 0, 1, 1, 2, 2}; case "JP": - return new int[] {0, 1, 1, 2, 2, 4}; - case "KH": - return new int[] {2, 1, 4, 2, 2, 2}; - case "CF": - case "KI": - return new int[] {4, 2, 4, 2, 2, 2}; - case "FK": + return new int[] {0, 3, 3, 3, 4, 4}; case "KE": - case "KP": - return new int[] {3, 2, 2, 2, 2, 2}; + return new int[] {3, 2, 2, 1, 2, 2}; + case "KH": + return new int[] {1, 0, 4, 2, 2, 2}; + case "CU": + case "KI": + return new int[] {4, 2, 4, 3, 2, 2}; + case "CD": + case "KM": + return new int[] {4, 3, 3, 2, 2, 2}; case "KR": - return new int[] {0, 1, 1, 3, 4, 4}; - case "CY": + return new int[] {0, 2, 2, 4, 4, 4}; case "KW": - return new int[] {1, 0, 0, 0, 0, 2}; + return new int[] {1, 0, 1, 0, 0, 2}; + case "BD": case "KZ": return new int[] {2, 1, 2, 2, 2, 2}; case "LA": return new int[] {1, 2, 1, 3, 2, 2}; + case "BS": case "LB": - return new int[] {3, 3, 2, 4, 2, 2}; + return new int[] {3, 2, 1, 2, 2, 2}; case "LK": - return new int[] {3, 1, 3, 3, 4, 2}; - case "CI": - case "DZ": + return new int[] {3, 2, 3, 4, 4, 2}; case "LR": - return new int[] {3, 4, 4, 4, 2, 2}; - case "LS": - return new int[] {3, 3, 2, 2, 2, 2}; - case "LT": - return new int[] {0, 0, 0, 0, 2, 2}; + return new int[] {3, 4, 3, 4, 2, 2}; case "LU": - return new int[] {1, 0, 3, 2, 1, 4}; + return new int[] {1, 1, 4, 2, 0, 2}; + case "CY": + case "HR": + case "LV": + return new int[] {1, 0, 0, 0, 0, 2}; case "MA": - return new int[] {3, 3, 1, 1, 2, 2}; + return new int[] {3, 3, 2, 1, 2, 2}; case "MC": return new int[] {0, 2, 2, 0, 2, 2}; + case "MD": + return new int[] {1, 0, 0, 0, 2, 2}; case "ME": - return new int[] {2, 0, 0, 1, 2, 2}; + return new int[] {2, 0, 0, 1, 1, 2}; + case "MH": + return new int[] {4, 2, 1, 3, 2, 2}; case "MK": - return new int[] {1, 0, 0, 1, 3, 2}; + return new int[] {2, 0, 0, 1, 3, 2}; case "MM": - return new int[] {2, 4, 2, 3, 2, 2}; + return new int[] {2, 2, 2, 3, 4, 2}; case "MN": return new int[] {2, 0, 1, 2, 2, 2}; case "MO": - case "MP": - return new int[] {0, 2, 4, 4, 2, 2}; - case "GP": + return new int[] {0, 2, 4, 4, 4, 2}; + case "KG": case "MQ": - return new int[] {2, 1, 2, 3, 2, 2}; - case "MU": - return new int[] {3, 1, 1, 2, 2, 2}; + return new int[] {2, 1, 1, 2, 2, 2}; + case "MR": + return new int[] {4, 2, 3, 4, 2, 2}; + case "DK": + case "EE": + case "HU": + case "LT": + case "MT": + return new int[] {0, 0, 0, 0, 0, 2}; case "MV": - return new int[] {3, 4, 1, 4, 2, 2}; + return new int[] {3, 4, 1, 3, 3, 2}; case "MW": return new int[] {4, 2, 3, 3, 2, 2}; case "MX": - return new int[] {2, 4, 3, 4, 2, 2}; + return new int[] {3, 4, 4, 4, 2, 2}; case "MY": - return new int[] {1, 0, 3, 1, 3, 2}; - case "MZ": - return new int[] {3, 1, 2, 1, 2, 2}; + return new int[] {1, 0, 4, 1, 2, 2}; + case "NA": + return new int[] {3, 4, 3, 2, 2, 2}; case "NC": - return new int[] {3, 3, 4, 4, 2, 2}; + return new int[] {3, 2, 3, 4, 2, 2}; case "NG": return new int[] {3, 4, 2, 1, 2, 2}; + case "NI": + return new int[] {2, 3, 4, 3, 2, 2}; case "NL": - return new int[] {0, 2, 2, 3, 0, 3}; - case "CZ": + return new int[] {0, 2, 3, 3, 0, 4}; case "NO": - return new int[] {0, 0, 2, 0, 1, 2}; + return new int[] {0, 1, 2, 1, 1, 2}; case "NP": - return new int[] {2, 2, 4, 3, 2, 2}; + return new int[] {2, 1, 4, 3, 2, 2}; case "NR": + return new int[] {4, 0, 3, 2, 2, 2}; case "NU": return new int[] {4, 2, 2, 1, 2, 2}; + case "NZ": + return new int[] {1, 0, 2, 2, 4, 2}; case "OM": return new int[] {2, 3, 1, 3, 4, 2}; - case "GU": + case "PA": + return new int[] {2, 3, 3, 3, 2, 2}; case "PE": - return new int[] {1, 2, 4, 4, 4, 2}; - case "CK": - case "PF": - return new int[] {2, 2, 2, 1, 2, 2}; - case "ML": + return new int[] {1, 2, 4, 4, 3, 2}; + case "AF": case "PG": - return new int[] {4, 3, 3, 2, 2, 2}; + return new int[] {4, 3, 3, 3, 2, 2}; case "PH": - return new int[] {2, 1, 3, 3, 3, 0}; - case "NZ": + return new int[] {2, 1, 3, 2, 2, 0}; case "PL": - return new int[] {1, 1, 2, 2, 4, 2}; + return new int[] {2, 1, 2, 2, 4, 2}; case "PR": - return new int[] {2, 0, 2, 1, 2, 1}; + return new int[] {2, 0, 2, 0, 2, 1}; case "PS": - return new int[] {3, 4, 1, 2, 2, 2}; + return new int[] {3, 4, 1, 4, 2, 2}; + case "PT": + return new int[] {1, 0, 0, 0, 1, 2}; case "PW": - return new int[] {2, 2, 4, 1, 2, 2}; - case "QA": - return new int[] {2, 4, 4, 4, 4, 2}; + return new int[] {2, 2, 4, 2, 2, 2}; + case "BL": case "MF": + case "PY": + return new int[] {1, 2, 2, 2, 2, 2}; + case "QA": + return new int[] {1, 4, 4, 4, 4, 2}; case "RE": - return new int[] {1, 2, 1, 2, 2, 2}; + return new int[] {1, 2, 2, 3, 1, 2}; case "RO": return new int[] {0, 0, 1, 2, 1, 2}; - case "MD": case "RS": - return new int[] {1, 0, 0, 0, 2, 2}; + return new int[] {2, 0, 0, 0, 2, 2}; case "RU": - return new int[] {1, 0, 0, 0, 4, 3}; + return new int[] {1, 0, 0, 0, 3, 3}; case "RW": - return new int[] {3, 4, 2, 0, 2, 2}; + return new int[] {3, 3, 1, 0, 2, 2}; + case "MU": case "SA": - return new int[] {3, 1, 1, 1, 2, 2}; + return new int[] {3, 1, 1, 2, 2, 2}; + case "CF": case "SB": - return new int[] {4, 2, 4, 3, 2, 2}; + return new int[] {4, 2, 4, 2, 2, 2}; + case "SC": + return new int[] {4, 3, 1, 1, 2, 2}; + case "SD": + return new int[] {4, 3, 4, 2, 2, 2}; + case "SE": + return new int[] {0, 1, 1, 1, 0, 2}; case "SG": - return new int[] {1, 1, 2, 2, 2, 1}; + return new int[] {2, 3, 3, 3, 3, 3}; case "AQ": case "ER": case "SH": return new int[] {4, 2, 2, 2, 2, 2}; - case "GR": - case "HR": - case "SI": - return new int[] {1, 0, 0, 0, 1, 2}; case "BG": - case "MT": - case "SK": + case "ES": + case "GR": + case "SI": return new int[] {0, 0, 0, 0, 1, 2}; - case "AX": - case "LI": - case "MS": - case "PM": - case "SM": - return new int[] {0, 2, 2, 2, 2, 2}; + case "IQ": + case "SJ": + return new int[] {3, 2, 2, 2, 2, 2}; + case "SK": + return new int[] {1, 1, 1, 1, 3, 2}; + case "GF": + case "PK": + case "SL": + return new int[] {3, 2, 3, 3, 2, 2}; + case "ET": case "SN": - return new int[] {4, 4, 4, 3, 2, 2}; + return new int[] {4, 4, 3, 2, 2, 2}; + case "SO": + return new int[] {3, 2, 2, 4, 4, 2}; case "SR": return new int[] {2, 4, 3, 0, 2, 2}; - case "SS": - return new int[] {4, 3, 2, 3, 2, 2}; case "ST": return new int[] {2, 2, 1, 2, 2, 2}; - case "NI": - case "PA": + case "PF": case "SV": - return new int[] {2, 3, 3, 3, 2, 2}; + return new int[] {2, 3, 3, 1, 2, 2}; case "SZ": - return new int[] {3, 3, 3, 4, 2, 2}; - case "SX": + return new int[] {4, 4, 3, 4, 2, 2}; case "TC": - return new int[] {1, 2, 1, 0, 2, 2}; + return new int[] {2, 2, 1, 3, 2, 2}; case "GA": case "TG": return new int[] {3, 4, 1, 0, 2, 2}; case "TH": - return new int[] {0, 2, 2, 3, 3, 4}; - case "TK": - return new int[] {2, 2, 2, 4, 2, 2}; - case "CU": + return new int[] {0, 1, 2, 1, 2, 2}; case "DJ": case "SY": case "TJ": - case "TL": return new int[] {4, 3, 4, 4, 2, 2}; - case "SC": + case "GL": + case "TK": + return new int[] {2, 2, 2, 4, 2, 2}; + case "TL": + return new int[] {4, 2, 4, 4, 2, 2}; + case "SS": case "TM": - return new int[] {4, 2, 1, 1, 2, 2}; - case "AZ": - case "GF": - case "LY": - case "PK": - case "SO": - case "TO": - return new int[] {3, 2, 3, 3, 2, 2}; + return new int[] {4, 2, 2, 3, 2, 2}; case "TR": - return new int[] {1, 1, 0, 0, 2, 2}; + return new int[] {1, 0, 0, 1, 3, 2}; case "TT": - return new int[] {1, 4, 1, 3, 2, 2}; - case "EE": - case "IS": - case "LV": - case "PT": - case "SE": + return new int[] {1, 4, 0, 0, 2, 2}; case "TW": - return new int[] {0, 0, 0, 0, 0, 2}; + return new int[] {0, 2, 0, 0, 0, 0}; + case "ML": case "TZ": - return new int[] {3, 4, 3, 2, 2, 2}; - case "IM": + return new int[] {3, 4, 2, 2, 2, 2}; case "UA": - return new int[] {0, 2, 1, 1, 2, 2}; - case "SL": + return new int[] {0, 1, 1, 2, 4, 2}; + case "LS": case "UG": - return new int[] {3, 3, 4, 3, 2, 2}; + return new int[] {3, 3, 3, 2, 2, 2}; case "US": - return new int[] {1, 0, 2, 2, 3, 1}; - case "AR": - case "KG": + return new int[] {1, 1, 4, 1, 3, 1}; case "TN": case "UY": return new int[] {2, 1, 1, 1, 2, 2}; case "UZ": - return new int[] {2, 2, 3, 4, 2, 2}; - case "BL": + return new int[] {2, 2, 3, 4, 3, 2}; + case "AX": case "CX": + case "LI": + case "MP": + case "MS": + case "PM": + case "SM": case "VA": - return new int[] {1, 2, 2, 2, 2, 2}; - case "AD": - case "BM": - case "BQ": + return new int[] {0, 2, 2, 2, 2, 2}; case "GD": - case "GL": case "KN": case "KY": case "LC": + case "SX": case "VC": return new int[] {1, 2, 0, 0, 2, 2}; case "VG": - return new int[] {2, 2, 1, 1, 2, 2}; - case "GG": + return new int[] {2, 2, 0, 1, 2, 2}; case "VI": - return new int[] {0, 2, 0, 1, 2, 2}; + return new int[] {0, 2, 1, 2, 2, 2}; case "VN": - return new int[] {0, 3, 3, 4, 2, 2}; - case "GH": - case "NA": + return new int[] {0, 0, 1, 2, 2, 1}; case "VU": - return new int[] {3, 3, 3, 2, 2, 2}; + return new int[] {4, 3, 3, 1, 2, 2}; case "IO": - case "MH": case "TV": case "WF": return new int[] {4, 2, 2, 4, 2, 2}; + case "BT": + case "MZ": case "WS": - return new int[] {3, 1, 3, 1, 2, 2}; - case "AL": + return new int[] {3, 1, 2, 1, 2, 2}; case "XK": - return new int[] {1, 1, 1, 1, 2, 2}; + return new int[] {1, 2, 1, 1, 2, 2}; case "BI": case "HT": - case "KM": case "MG": case "NE": - case "SD": case "TD": case "VE": case "YE": return new int[] {4, 4, 4, 4, 2, 2}; - case "JE": case "YT": - return new int[] {4, 2, 2, 3, 2, 2}; + return new int[] {2, 3, 3, 4, 2, 2}; case "ZA": - return new int[] {3, 2, 2, 1, 1, 2}; + return new int[] {2, 3, 2, 1, 2, 2}; case "ZM": - return new int[] {3, 3, 4, 2, 2, 2}; - case "MR": + return new int[] {4, 4, 4, 3, 3, 2}; + case "LY": + case "TO": case "ZW": - return new int[] {4, 2, 4, 4, 2, 2}; + return new int[] {3, 2, 4, 3, 2, 2}; default: return new int[] {2, 2, 2, 2, 2, 2}; } From 61eeb8b15ec0f18e64c60633f90093bd0ae7dbcc Mon Sep 17 00:00:00 2001 From: bachinger Date: Tue, 10 Jan 2023 21:19:55 +0000 Subject: [PATCH 078/104] Add focusSkipButtonWhenAvailable to focus UI on ATV For TV devices the skip button needs to have the focus to be accessible with the remote control. This property makes this configurable while being set to true by default. PiperOrigin-RevId: 501077608 (cherry picked from commit d2898b70ead3b2ad1244a730c3b456146755c138) --- .../ImaServerSideAdInsertionMediaSource.java | 36 ++++++++++++++++--- .../android/exoplayer2/ext/ima/ImaUtil.java | 3 ++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index ea307b294b..27ea2275ce 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -67,6 +67,7 @@ import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; +import com.google.android.exoplayer2.ext.ima.ImaUtil.ServerSideAdInsertionConfiguration; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; @@ -191,6 +192,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou @Nullable private AdErrorEvent.AdErrorListener adErrorListener; private State state; private ImmutableList companionAdSlots; + private boolean focusSkipButtonWhenAvailable; /** * Creates an instance. @@ -203,6 +205,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou this.adViewProvider = adViewProvider; companionAdSlots = ImmutableList.of(); state = new State(ImmutableMap.of()); + focusSkipButtonWhenAvailable = true; } /** @@ -272,6 +275,22 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou 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) + */ + @CanIgnoreReturnValue + public AdsLoader.Builder setFocusSkipButtonWhenAvailable( + boolean focusSkipButtonWhenAvailable) { + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; + return this; + } + /** Returns a new {@link AdsLoader}. */ public AdsLoader build() { @Nullable ImaSdkSettings imaSdkSettings = this.imaSdkSettings; @@ -279,13 +298,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); imaSdkSettings.setLanguage(Util.getSystemLanguageCodes()[0]); } - ImaUtil.ServerSideAdInsertionConfiguration configuration = - new ImaUtil.ServerSideAdInsertionConfiguration( + ServerSideAdInsertionConfiguration configuration = + new ServerSideAdInsertionConfiguration( adViewProvider, imaSdkSettings, adEventListener, adErrorListener, companionAdSlots, + focusSkipButtonWhenAvailable, imaSdkSettings.isDebugMode()); return new AdsLoader(context, configuration, state); } @@ -352,7 +372,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou } } - private final ImaUtil.ServerSideAdInsertionConfiguration configuration; + private final ServerSideAdInsertionConfiguration configuration; private final Context context; private final Map mediaSourceResources; @@ -361,7 +381,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou @Nullable private Player player; private AdsLoader( - Context context, ImaUtil.ServerSideAdInsertionConfiguration configuration, State state) { + Context context, ServerSideAdInsertionConfiguration configuration, State state) { this.context = context.getApplicationContext(); this.configuration = configuration; mediaSourceResources = new HashMap<>(); @@ -502,6 +522,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou StreamManagerLoadable streamManagerLoadable = new StreamManagerLoadable( sdkAdsLoader, + adsLoader.configuration, streamRequest, streamPlayer, applicationAdErrorListener, @@ -930,6 +951,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou implements Loadable, AdsLoadedListener, AdErrorListener { private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; + private final ServerSideAdInsertionConfiguration serverSideAdInsertionConfiguration; private final StreamRequest request; private final StreamPlayer streamPlayer; @Nullable private final AdErrorListener adErrorListener; @@ -946,11 +968,13 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou /** Creates an instance. */ private StreamManagerLoadable( com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader, + ServerSideAdInsertionConfiguration serverSideAdInsertionConfiguration, StreamRequest request, StreamPlayer streamPlayer, @Nullable AdErrorListener adErrorListener, int loadVideoTimeoutMs) { this.adsLoader = adsLoader; + this.serverSideAdInsertionConfiguration = serverSideAdInsertionConfiguration; this.request = request; this.streamPlayer = streamPlayer; this.adErrorListener = adErrorListener; @@ -1027,6 +1051,8 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou AdsRenderingSettings adsRenderingSettings = ImaSdkFactory.getInstance().createAdsRenderingSettings(); adsRenderingSettings.setLoadVideoTimeout(loadVideoTimeoutMs); + adsRenderingSettings.setFocusSkipButtonWhenAvailable( + serverSideAdInsertionConfiguration.focusSkipButtonWhenAvailable); // After initialization completed the streamUri will be reported to the streamPlayer. streamManager.init(adsRenderingSettings); this.streamManager = streamManager; @@ -1259,7 +1285,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou private static StreamDisplayContainer createStreamDisplayContainer( ImaSdkFactory imaSdkFactory, - ImaUtil.ServerSideAdInsertionConfiguration config, + ServerSideAdInsertionConfiguration config, StreamPlayer streamPlayer) { StreamDisplayContainer container = ImaSdkFactory.createStreamDisplayContainer( diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index 7a5e90c149..a9ae28d26c 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -166,6 +166,7 @@ import java.util.Set; @Nullable public final AdEvent.AdEventListener applicationAdEventListener; @Nullable public final AdErrorEvent.AdErrorListener applicationAdErrorListener; public final ImmutableList companionAdSlots; + public final boolean focusSkipButtonWhenAvailable; public final boolean debugModeEnabled; public ServerSideAdInsertionConfiguration( @@ -174,12 +175,14 @@ import java.util.Set; @Nullable AdEvent.AdEventListener applicationAdEventListener, @Nullable AdErrorEvent.AdErrorListener applicationAdErrorListener, List companionAdSlots, + boolean focusSkipButtonWhenAvailable, boolean debugModeEnabled) { this.imaSdkSettings = imaSdkSettings; this.adViewProvider = adViewProvider; this.applicationAdEventListener = applicationAdEventListener; this.applicationAdErrorListener = applicationAdErrorListener; this.companionAdSlots = ImmutableList.copyOf(companionAdSlots); + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; this.debugModeEnabled = debugModeEnabled; } } From b1b9c1207bf9a7a97948f5b9eb1c0e36c2357f42 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 11 Jan 2023 18:39:11 +0000 Subject: [PATCH 079/104] Request notification permission in demo app for API 33+ Starting with API 33 the POST_NOTIFICATION permission needs to be requested at runtime or the notification is not shown. Note that with an app with targetSdkVersion < 33 but on a device with API 33 the notification permission is automatically requested when the app starts for the first time. If the user does not grant the permission, requesting the permission at runtime result in an empty array of grant results. Issue: google/ExoPlayer#10884 PiperOrigin-RevId: 501320632 (cherry picked from commit 6bacbaac548d612e85647b4d13c22dae0401447e) --- demos/main/src/main/AndroidManifest.xml | 1 + .../demo/SampleChooserActivity.java | 62 ++++++++++++++++--- demos/main/src/main/res/values/strings.xml | 2 + 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 36b3729453..adad57b650 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index e47c273455..85c5f218ba 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -41,7 +42,9 @@ import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem.ClippingConfiguration; @@ -75,6 +78,7 @@ public class SampleChooserActivity extends AppCompatActivity private static final String TAG = "SampleChooserActivity"; private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position"; private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position"; + private static final int POST_NOTIFICATION_PERMISSION_REQUEST_CODE = 100; private String[] uris; private boolean useExtensionRenderers; @@ -82,6 +86,8 @@ public class SampleChooserActivity extends AppCompatActivity private SampleAdapter sampleAdapter; private MenuItem preferExtensionDecodersMenuItem; private ExpandableListView sampleListView; + @Nullable private MediaItem downloadMediaItemWaitingForNotificationPermission; + private boolean notificationPermissionToastShown; @Override public void onCreate(Bundle savedInstanceState) { @@ -170,12 +176,34 @@ public class SampleChooserActivity extends AppCompatActivity public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == POST_NOTIFICATION_PERMISSION_REQUEST_CODE) { + handlePostNotificationPermissionGrantResults(grantResults); + } else { + handleExternalStoragePermissionGrantResults(grantResults); + } + } + + private void handlePostNotificationPermissionGrantResults(int[] grantResults) { + if (!notificationPermissionToastShown + && (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED)) { + Toast.makeText( + getApplicationContext(), R.string.post_notification_not_granted, Toast.LENGTH_LONG) + .show(); + notificationPermissionToastShown = true; + } + if (downloadMediaItemWaitingForNotificationPermission != null) { + // Download with or without permission to post notifications. + toggleDownload(downloadMediaItemWaitingForNotificationPermission); + downloadMediaItemWaitingForNotificationPermission = null; + } + } + + private void handleExternalStoragePermissionGrantResults(int[] grantResults) { if (grantResults.length == 0) { // Empty results are triggered if a permission is requested while another request was already // pending and can be safely ignored in this case. return; - } - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadSample(); } else { Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) @@ -242,15 +270,26 @@ public class SampleChooserActivity extends AppCompatActivity if (downloadUnsupportedStringId != 0) { Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG) .show(); + } else if (!notificationPermissionToastShown + && Util.SDK_INT >= 33 + && checkSelfPermission(Api33.getPostNotificationPermissionString()) + != PackageManager.PERMISSION_GRANTED) { + downloadMediaItemWaitingForNotificationPermission = playlistHolder.mediaItems.get(0); + requestPermissions( + new String[] {Api33.getPostNotificationPermissionString()}, + /* requestCode= */ POST_NOTIFICATION_PERMISSION_REQUEST_CODE); } else { - RenderersFactory renderersFactory = - DemoUtil.buildRenderersFactory( - /* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem)); - downloadTracker.toggleDownload( - getSupportFragmentManager(), playlistHolder.mediaItems.get(0), renderersFactory); + toggleDownload(playlistHolder.mediaItems.get(0)); } } + private void toggleDownload(MediaItem mediaItem) { + RenderersFactory renderersFactory = + DemoUtil.buildRenderersFactory( + /* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem)); + downloadTracker.toggleDownload(getSupportFragmentManager(), mediaItem, renderersFactory); + } + private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) { if (playlistHolder.mediaItems.size() > 1) { return R.string.download_playlist_unsupported; @@ -627,4 +666,13 @@ public class SampleChooserActivity extends AppCompatActivity this.playlists = new ArrayList<>(); } } + + @RequiresApi(33) + private static class Api33 { + + @DoNotInline + public static String getPostNotificationPermissionString() { + return Manifest.permission.POST_NOTIFICATIONS; + } + } } diff --git a/demos/main/src/main/res/values/strings.xml b/demos/main/src/main/res/values/strings.xml index 49441ef7da..ce9c90d0c2 100644 --- a/demos/main/src/main/res/values/strings.xml +++ b/demos/main/src/main/res/values/strings.xml @@ -45,6 +45,8 @@ One or more sample lists failed to load + Notifications suppressed. Grant permission to see download notifications. + Failed to start download Failed to obtain offline license From fc4415bab4f31f94aa972d481da05177cb4bfac2 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 11 Jan 2023 20:19:00 +0000 Subject: [PATCH 080/104] Document that `DownloadService` needs notification permissions Starting with Android 13 (API 33) an app needs to request the permission to post notifications or notifications are suppressed. This change documents this in the class level JavaDoc of the `DownloadService`. Issue: google/ExoPlayer#10884 PiperOrigin-RevId: 501346908 (cherry picked from commit 055ed77433944fc31d431d65e09514e8b3e250f1) --- .../android/exoplayer2/offline/DownloadService.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java index 0bdde5e9b0..03b05bf0ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java @@ -39,7 +39,16 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -/** A {@link Service} for downloading media. */ +/** + * A {@link Service} for downloading media. + * + *

    Apps with target SDK 33 and greater need to add the {@code + * android.permission.POST_NOTIFICATIONS} permission to the manifest and request the permission at + * runtime before starting downloads. Without that permission granted by the user, notifications + * posted by this service are not displayed. See the + * official UI guide for more detailed information. + */ public abstract class DownloadService extends Service { /** From 8f37ad6bf8cff3c0fb8ea2f35e7f8ff89dba5a4e Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 11 Jan 2023 23:46:58 +0000 Subject: [PATCH 081/104] Add AdsLoader.focusSkipButton() This method allows to call through to `StreamManager.focus()` of the currently playing SSAI stream. PiperOrigin-RevId: 501399144 (cherry picked from commit 0ba0c0ed43fbcf4a57f6fc91829ba00d72e3672f) --- .../ImaServerSideAdInsertionMediaSource.java | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index 27ea2275ce..d97e107c37 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -374,8 +374,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou private final ServerSideAdInsertionConfiguration configuration; private final Context context; - private final Map - mediaSourceResources; + private final Map mediaSourceResources; private final Map adPlaybackStateMap; @Nullable private Player player; @@ -401,6 +400,35 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou this.player = player; } + /** + * Puts the focus on the skip button, if a skip button is present and an ad is playing. + * + * @see StreamManager#focus() + */ + public void focusSkipButton() { + if (player == null) { + return; + } + if (player.getPlaybackState() != Player.STATE_IDLE + && player.getPlaybackState() != Player.STATE_ENDED + && player.getMediaItemCount() > 0) { + int currentPeriodIndex = player.getCurrentPeriodIndex(); + Object adsId = + player + .getCurrentTimeline() + .getPeriod(currentPeriodIndex, new Timeline.Period()) + .getAdsId(); + if (adsId instanceof String) { + MediaSourceResourceHolder mediaSourceResourceHolder = mediaSourceResources.get(adsId); + if (mediaSourceResourceHolder != null + && mediaSourceResourceHolder.imaServerSideAdInsertionMediaSource.streamManager + != null) { + mediaSourceResourceHolder.imaServerSideAdInsertionMediaSource.streamManager.focus(); + } + } + } + } + /** * Releases resources. * @@ -427,7 +455,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou StreamPlayer streamPlayer, com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader) { mediaSourceResources.put( - mediaSource, new MediaSourceResourceHolder(mediaSource, streamPlayer, adsLoader)); + mediaSource.adsId, new MediaSourceResourceHolder(mediaSource, streamPlayer, adsLoader)); } private AdPlaybackState getAdPlaybackState(String adsId) { From 4b3c74fb37da400cdad85e75245b091869c90a72 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 16 Jan 2023 16:38:12 +0000 Subject: [PATCH 082/104] Clarify what default settings are being used for SSAI AdsLoader PiperOrigin-RevId: 502388865 (cherry picked from commit 26e1a28176d8520890802c0a50aec8bc710eb2c3) --- .../ext/ima/ImaServerSideAdInsertionMediaSource.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index d97e107c37..8137dcf504 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -211,7 +211,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou /** * Sets the IMA SDK settings. * - *

    If this method is not called the default settings will be used. + *

    If this method is not called, the {@linkplain ImaSdkFactory#createImaSdkSettings() + * default settings} will be used with the language set to {@linkplain + * Util#getSystemLanguageCodes() the preferred system language}. * * @param imaSdkSettings The {@link ImaSdkSettings}. * @return This builder, for convenience. From cebc0fa3745c7a66a9df4601bccd6b707800d9c7 Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 17 Jan 2023 14:42:18 +0000 Subject: [PATCH 083/104] Disables play/pause button when there's nothing to play PiperOrigin-RevId: 502571320 (cherry picked from commit 345f2345c74ccbbf3dd49f125b1b81938b0a9995) --- .../android/exoplayer2/ui/StyledPlayerControlView.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index e8d72983f0..55e9680b06 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -986,6 +986,9 @@ public class StyledPlayerControlView extends FrameLayout { ((ImageView) playPauseButton) .setImageDrawable(getDrawable(getContext(), resources, drawableRes)); playPauseButton.setContentDescription(resources.getString(stringRes)); + + boolean enablePlayPause = shouldEnablePlayPauseButton(); + updateButton(enablePlayPause, playPauseButton); } } @@ -1503,6 +1506,10 @@ public class StyledPlayerControlView extends FrameLayout { } } + private boolean shouldEnablePlayPauseButton() { + return player != null && !player.getCurrentTimeline().isEmpty(); + } + private boolean shouldShowPauseButton() { return player != null && player.getPlaybackState() != Player.STATE_ENDED From 7f20729117e55c55552867b6a341a1d444e13c54 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 17 Jan 2023 17:30:02 +0000 Subject: [PATCH 084/104] Make availableCommands known when bundling PlayerInfo When bundling PlayerInfo, we remove data when the controller is not allowed to access this data via getters. We also remove data for performance reasons. In the toBundle() method, it's currently hard to make the connection between allowed commands and filtering, because the values are checked at a different place. This can be made more readable by forwarding the applicable Commands directly. The only functional fix is to filter the Timeline when sending the first PlayerInfo after a connecting a controller if the command to get the Timeline is not available. This also allows us to remove a path to filter MediaItems from Timelines as it isn't used. PiperOrigin-RevId: 502607391 (cherry picked from commit 5461d5cbf1cee5bf85bb3b7ae4f4f22c2c2538b8) --- .../google/android/exoplayer2/Timeline.java | 43 +++---------------- .../android/exoplayer2/TimelineTest.java | 6 +-- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 51df3f0818..7acd4863ab 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -429,17 +429,16 @@ public abstract class Timeline implements Bundleable { private static final String FIELD_POSITION_IN_FIRST_PERIOD_US = Util.intToStringMaxRadix(13); /** - * Returns a {@link Bundle} representing the information stored in this object. + * {@inheritDoc} * *

    It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance * restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the * instance will be {@code null}. - * - * @param excludeMediaItem Whether to exclude {@link #mediaItem} of window. */ - public Bundle toBundle(boolean excludeMediaItem) { + @Override + public Bundle toBundle() { Bundle bundle = new Bundle(); - if (!excludeMediaItem) { + if (!MediaItem.EMPTY.equals(mediaItem)) { bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); } if (presentationStartTimeMs != C.TIME_UNSET) { @@ -483,19 +482,6 @@ public abstract class Timeline implements Bundleable { return bundle; } - /** - * {@inheritDoc} - * - *

    It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance - * restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the - * instance will be {@code null}. - */ - // TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise. - @Override - public Bundle toBundle() { - return toBundle(/* excludeMediaItem= */ false); - } - /** * Object that can restore {@link Period} from a {@link Bundle}. * @@ -1384,17 +1370,14 @@ public abstract class Timeline implements Bundleable { *

    The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of * an instance restored by {@link #CREATOR} may have missing fields as described in {@link * Window#toBundle()} and {@link Period#toBundle()}. - * - * @param excludeMediaItems Whether to exclude all {@link Window#mediaItem media items} of windows - * in the timeline. */ - public final Bundle toBundle(boolean excludeMediaItems) { + @Override + public final Bundle toBundle() { List windowBundles = new ArrayList<>(); int windowCount = getWindowCount(); Window window = new Window(); for (int i = 0; i < windowCount; i++) { - windowBundles.add( - getWindow(i, window, /* defaultPositionProjectionUs= */ 0).toBundle(excludeMediaItems)); + windowBundles.add(getWindow(i, window, /* defaultPositionProjectionUs= */ 0).toBundle()); } List periodBundles = new ArrayList<>(); @@ -1421,18 +1404,6 @@ public abstract class Timeline implements Bundleable { return bundle; } - /** - * {@inheritDoc} - * - *

    The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of - * an instance restored by {@link #CREATOR} may have missing fields as described in {@link - * Window#toBundle()} and {@link Period#toBundle()}. - */ - @Override - public final Bundle toBundle() { - return toBundle(/* excludeMediaItems= */ false); - } - /** * Object that can restore a {@link Timeline} from a {@link Bundle}. * diff --git a/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java index 57fffe609e..4d8e305f2c 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -351,10 +351,8 @@ public class TimelineTest { Bundle windowBundle = window.toBundle(); - // Check that default values are skipped when bundling. MediaItem key is not added to the bundle - // only when excludeMediaItem is true. - assertThat(windowBundle.keySet()).hasSize(1); - assertThat(window.toBundle(/* excludeMediaItem= */ true).keySet()).isEmpty(); + // Check that default values are skipped when bundling. + assertThat(windowBundle.keySet()).isEmpty(); Timeline.Window restoredWindow = Timeline.Window.CREATOR.fromBundle(windowBundle); From 31aae7782e3aa331e399d7bbdb50d58d79a8b00f Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 18 Jan 2023 10:54:42 +0000 Subject: [PATCH 085/104] Fix javadoc references to `writeSampleData` PiperOrigin-RevId: 502821506 (cherry picked from commit 8fcd6bbffc6d67cf007d09f0230256b3ed551fd8) --- .../android/exoplayer2/extractor/mkv/MatroskaExtractor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index eb60880cda..504656e4e8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -1650,8 +1650,8 @@ public class MatroskaExtractor implements Extractor { } /** - * Called by {@link #writeSampleData(ExtractorInput, Track, int)} when the sample has been - * written. Returns the final sample size and resets state for the next sample. + * Called by {@link #writeSampleData(ExtractorInput, Track, int, boolean)} when the sample has + * been written. Returns the final sample size and resets state for the next sample. */ private int finishWriteSampleData() { int sampleSize = sampleBytesWritten; @@ -1659,7 +1659,7 @@ public class MatroskaExtractor implements Extractor { return sampleSize; } - /** Resets state used by {@link #writeSampleData(ExtractorInput, Track, int)}. */ + /** Resets state used by {@link #writeSampleData(ExtractorInput, Track, int, boolean)}. */ private void resetWriteSampleData() { sampleBytesRead = 0; sampleBytesWritten = 0; From ea1301a53128f9eaf1d8fee690613fe2b6b06b81 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 18 Jan 2023 13:49:01 +0000 Subject: [PATCH 086/104] Correctly filter PlayerInfo by available getter commands. When bundling PlayerInfo, we need to remove information if the controller is not allowed to access it. This was only partially done at the moment. PiperOrigin-RevId: 502852798 (cherry picked from commit 50f066d63425f44f51108d654006f470d76fb042) --- .../com/google/android/exoplayer2/Player.java | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 02b552769e..1e83b03ab1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -286,16 +286,30 @@ public interface Player { */ @Override public Bundle toBundle() { + return toBundle(/* canAccessCurrentMediaItem= */ true, /* canAccessTimeline= */ true); + } + + /** + * Returns a {@link Bundle} representing the information stored in this object, filtered by + * available commands. + * + * @param canAccessCurrentMediaItem Whether the {@link Bundle} should contain information + * accessbile with {@link #COMMAND_GET_CURRENT_MEDIA_ITEM}. + * @param canAccessTimeline Whether the {@link Bundle} should contain information accessbile + * with {@link #COMMAND_GET_TIMELINE}. + */ + public Bundle toBundle(boolean canAccessCurrentMediaItem, boolean canAccessTimeline) { Bundle bundle = new Bundle(); - bundle.putInt(FIELD_MEDIA_ITEM_INDEX, mediaItemIndex); - if (mediaItem != null) { + bundle.putInt(FIELD_MEDIA_ITEM_INDEX, canAccessTimeline ? mediaItemIndex : 0); + if (mediaItem != null && canAccessCurrentMediaItem) { bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); } - bundle.putInt(FIELD_PERIOD_INDEX, periodIndex); - bundle.putLong(FIELD_POSITION_MS, positionMs); - bundle.putLong(FIELD_CONTENT_POSITION_MS, contentPositionMs); - bundle.putInt(FIELD_AD_GROUP_INDEX, adGroupIndex); - bundle.putInt(FIELD_AD_INDEX_IN_AD_GROUP, adIndexInAdGroup); + bundle.putInt(FIELD_PERIOD_INDEX, canAccessTimeline ? periodIndex : 0); + bundle.putLong(FIELD_POSITION_MS, canAccessCurrentMediaItem ? positionMs : 0); + bundle.putLong(FIELD_CONTENT_POSITION_MS, canAccessCurrentMediaItem ? contentPositionMs : 0); + bundle.putInt(FIELD_AD_GROUP_INDEX, canAccessCurrentMediaItem ? adGroupIndex : C.INDEX_UNSET); + bundle.putInt( + FIELD_AD_INDEX_IN_AD_GROUP, canAccessCurrentMediaItem ? adIndexInAdGroup : C.INDEX_UNSET); return bundle; } @@ -303,15 +317,14 @@ public interface Player { public static final Creator CREATOR = PositionInfo::fromBundle; private static PositionInfo fromBundle(Bundle bundle) { - int mediaItemIndex = bundle.getInt(FIELD_MEDIA_ITEM_INDEX, /* defaultValue= */ C.INDEX_UNSET); + int mediaItemIndex = bundle.getInt(FIELD_MEDIA_ITEM_INDEX, /* defaultValue= */ 0); @Nullable Bundle mediaItemBundle = bundle.getBundle(FIELD_MEDIA_ITEM); @Nullable MediaItem mediaItem = mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle); - int periodIndex = bundle.getInt(FIELD_PERIOD_INDEX, /* defaultValue= */ C.INDEX_UNSET); - long positionMs = bundle.getLong(FIELD_POSITION_MS, /* defaultValue= */ C.TIME_UNSET); - long contentPositionMs = - bundle.getLong(FIELD_CONTENT_POSITION_MS, /* defaultValue= */ C.TIME_UNSET); + int periodIndex = bundle.getInt(FIELD_PERIOD_INDEX, /* defaultValue= */ 0); + long positionMs = bundle.getLong(FIELD_POSITION_MS, /* defaultValue= */ 0); + long contentPositionMs = bundle.getLong(FIELD_CONTENT_POSITION_MS, /* defaultValue= */ 0); int adGroupIndex = bundle.getInt(FIELD_AD_GROUP_INDEX, /* defaultValue= */ C.INDEX_UNSET); int adIndexInAdGroup = bundle.getInt(FIELD_AD_INDEX_IN_AD_GROUP, /* defaultValue= */ C.INDEX_UNSET); @@ -2268,6 +2281,9 @@ public interface Player { *

    Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ boolean hasPreviousMediaItem(); @@ -2350,6 +2366,9 @@ public interface Player { *

    Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more * details. + * + *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + * #getAvailableCommands() available}. */ boolean hasNextMediaItem(); From b1693860fa626361d0c8aebcf9e2dc0c11c41b88 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 19 Jan 2023 09:50:52 +0000 Subject: [PATCH 087/104] Extend command GET_CURRENT_MEDIA_ITEM to more methods. We currently only document it for the getCurrentMediaItem(), but the command was always meant to cover all information about the current media item and the position therein. To correctly hide information for controllers, we need to filter the Timeline when bundling the PlayerInfo class if only this command is available. PiperOrigin-RevId: 503098124 (cherry picked from commit 5e9c9ed2346d7fcb10bfa0ffa5f253d7fdc7acc8) --- .../com/google/android/exoplayer2/Player.java | 70 ++++++++++++++++--- .../google/android/exoplayer2/Timeline.java | 32 +++++++++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 1e83b03ab1..cc57a5becd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -1613,10 +1613,28 @@ public interface Player { int COMMAND_SET_REPEAT_MODE = 15; /** - * Command to get the currently playing {@link MediaItem}. + * Command to get information about the currently playing {@link MediaItem}. * - *

    The {@link #getCurrentMediaItem()} method must only be called if this command is {@linkplain - * #isCommandAvailable(int) available}. + *

    The following methods must only be called if this command is {@linkplain + * #isCommandAvailable(int) available}: + * + *

      + *
    • {@link #getCurrentMediaItem()} + *
    • {@link #isCurrentMediaItemDynamic()} + *
    • {@link #isCurrentMediaItemLive()} + *
    • {@link #isCurrentMediaItemSeekable()} + *
    • {@link #getCurrentLiveOffset()} + *
    • {@link #getDuration()} + *
    • {@link #getCurrentPosition()} + *
    • {@link #getBufferedPosition()} + *
    • {@link #getContentDuration()} + *
    • {@link #getContentPosition()} + *
    • {@link #getContentBufferedPosition()} + *
    • {@link #getTotalBufferedDuration()} + *
    • {@link #isPlayingAd()} + *
    • {@link #getCurrentAdGroupIndex()} + *
    • {@link #getCurrentAdIndexInAdGroup()} + *
    */ int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; @@ -1636,8 +1654,6 @@ public interface Player { *
  • {@link #getPreviousMediaItemIndex()} *
  • {@link #hasPreviousMediaItem()} *
  • {@link #hasNextMediaItem()} - *
  • {@link #getCurrentAdGroupIndex()} - *
  • {@link #getCurrentAdIndexInAdGroup()} * */ int COMMAND_GET_TIMELINE = 17; @@ -2667,18 +2683,27 @@ public interface Player { /** * Returns the duration of the current content or ad in milliseconds, or {@link C#TIME_UNSET} if * the duration is not known. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getDuration(); /** * Returns the playback position in the current content or ad, in milliseconds, or the prospective * position in milliseconds if the {@link #getCurrentTimeline() current timeline} is empty. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getCurrentPosition(); /** * Returns an estimate of the position in the current content or ad up to which data is buffered, * in milliseconds. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getBufferedPosition(); @@ -2692,6 +2717,9 @@ public interface Player { /** * Returns an estimate of the total buffered duration from the current position, in milliseconds. * This includes pre-buffered data for subsequent ads and {@linkplain MediaItem media items}. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getTotalBufferedDuration(); @@ -2705,6 +2733,9 @@ public interface Player { * Returns whether the current {@link MediaItem} is dynamic (may change when the {@link Timeline} * is updated), or {@code false} if the {@link Timeline} is empty. * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @see Timeline.Window#isDynamic */ boolean isCurrentMediaItemDynamic(); @@ -2719,6 +2750,9 @@ public interface Player { * Returns whether the current {@link MediaItem} is live, or {@code false} if the {@link Timeline} * is empty. * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @see Timeline.Window#isLive() */ boolean isCurrentMediaItemLive(); @@ -2733,6 +2767,9 @@ public interface Player { * *

    Note that this offset may rely on an accurate local time, so this method may return an * incorrect value if the difference between system clock and server clock is unknown. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getCurrentLiveOffset(); @@ -2746,18 +2783,26 @@ public interface Player { * Returns whether the current {@link MediaItem} is seekable, or {@code false} if the {@link * Timeline} is empty. * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + * * @see Timeline.Window#isSeekable */ boolean isCurrentMediaItemSeekable(); - /** Returns whether the player is currently playing an ad. */ + /** + * Returns whether the player is currently playing an ad. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. + */ boolean isPlayingAd(); /** * If {@link #isPlayingAd()} returns true, returns the index of the ad group in the period * currently being played. Returns {@link C#INDEX_UNSET} otherwise. * - *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain * #getAvailableCommands() available}. */ int getCurrentAdGroupIndex(); @@ -2766,7 +2811,7 @@ public interface Player { * If {@link #isPlayingAd()} returns true, returns the index of the ad in its ad group. Returns * {@link C#INDEX_UNSET} otherwise. * - *

    This method must only be called if {@link #COMMAND_GET_TIMELINE} is {@linkplain + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain * #getAvailableCommands() available}. */ int getCurrentAdIndexInAdGroup(); @@ -2775,6 +2820,9 @@ public interface Player { * If {@link #isPlayingAd()} returns {@code true}, returns the duration of the current content in * milliseconds, or {@link C#TIME_UNSET} if the duration is not known. If there is no ad playing, * the returned duration is the same as that returned by {@link #getDuration()}. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getContentDuration(); @@ -2782,6 +2830,9 @@ public interface Player { * If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be * played once all ads in the ad group have finished playing, in milliseconds. If there is no ad * playing, the returned position is the same as that returned by {@link #getCurrentPosition()}. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getContentPosition(); @@ -2789,6 +2840,9 @@ public interface Player { * If {@link #isPlayingAd()} returns {@code true}, returns an estimate of the content position in * the current content up to which data is buffered, in milliseconds. If there is no ad playing, * the returned position is the same as that returned by {@link #getBufferedPosition()}. + * + *

    This method must only be called if {@link #COMMAND_GET_CURRENT_MEDIA_ITEM} is {@linkplain + * #getAvailableCommands() available}. */ long getContentBufferedPosition(); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 7acd4863ab..6fb855cc25 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -1404,6 +1404,38 @@ public abstract class Timeline implements Bundleable { return bundle; } + /** + * Returns a {@link Bundle} containing just the specified {@link Window}. + * + *

    The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of + * an instance restored by {@link #CREATOR} may have missing fields as described in {@link + * Window#toBundle()} and {@link Period#toBundle()}. + * + * @param windowIndex The index of the {@link Window} to include in the {@link Bundle}. + */ + public final Bundle toBundleWithOneWindowOnly(int windowIndex) { + Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0); + + List periodBundles = new ArrayList<>(); + Period period = new Period(); + for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) { + getPeriod(i, period, /* setIds= */ false); + period.windowIndex = 0; + periodBundles.add(period.toBundle()); + } + + window.lastPeriodIndex = window.lastPeriodIndex - window.firstPeriodIndex; + window.firstPeriodIndex = 0; + Bundle windowBundle = window.toBundle(); + + Bundle bundle = new Bundle(); + BundleUtil.putBinder( + bundle, FIELD_WINDOWS, new BundleListRetriever(ImmutableList.of(windowBundle))); + BundleUtil.putBinder(bundle, FIELD_PERIODS, new BundleListRetriever(periodBundles)); + bundle.putIntArray(FIELD_SHUFFLED_WINDOW_INDICES, new int[] {0}); + return bundle; + } + /** * Object that can restore a {@link Timeline} from a {@link Bundle}. * From 197109eb6990c9c8944b4d31a0393e5721e4fbb1 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 19 Jan 2023 16:56:32 +0000 Subject: [PATCH 088/104] Explicitly document most Player.Listener methods in terms of getters This makes it implicitly clear that if the value of a getter changes due to a change in command availability then the listener will be invoked, without needing to explicitly document every command on every listener method. #minor-release PiperOrigin-RevId: 503178383 (cherry picked from commit aa23920e148191f36b4d9f9d1fd9924e13855762) --- .../com/google/android/exoplayer2/Player.java | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index cc57a5becd..91dfe44dfd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -601,9 +601,15 @@ public interface Player { } /** - * Listener of all changes in the Player. + * Listener for changes in a {@link Player}. * *

    All methods have no-op default implementations to allow selective overrides. + * + *

    If the return value of a {@link Player} getter changes due to a change in {@linkplain + * #onAvailableCommandsChanged(Commands) command availability}, the corresponding listener + * method(s) will be invoked. If the return value of a {@link Player} getter does not change + * because the corresponding command is {@linkplain #onAvailableCommandsChanged(Commands) not + * available}, the corresponding listener method will not be invoked. */ interface Listener { @@ -613,9 +619,6 @@ public interface Player { *

    State changes and events that happen within one {@link Looper} message queue iteration are * reported together and only after all individual callbacks were triggered. * - *

    Only state changes represented by {@linkplain Event events} are reported through this - * method. - * *

    Listeners should prefer this method over individual callbacks in the following cases: * *

      @@ -641,7 +644,7 @@ public interface Player { default void onEvents(Player player, Events events) {} /** - * Called when the timeline has been refreshed. + * Called when the value of {@link Player#getCurrentTimeline()} changes. * *

      Note that the current {@link MediaItem} or playback position may change as a result of a * timeline change. If playback can't continue smoothly because of this timeline change, a @@ -660,9 +663,8 @@ public interface Player { * Called when playback transitions to a media item or starts repeating a media item according * to the current {@link #getRepeatMode() repeat mode}. * - *

      Note that this callback is also called when the playlist becomes non-empty or empty as a - * consequence of a playlist change or {@linkplain #onAvailableCommandsChanged(Commands) a - * change in available commands}. + *

      Note that this callback is also called when the value of {@link #getCurrentTimeline()} + * becomes non-empty or empty. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -674,7 +676,7 @@ public interface Player { @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {} /** - * Called when the tracks change. + * Called when the value of {@link Player#getCurrentTracks()} changes. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -684,14 +686,7 @@ public interface Player { default void onTracksChanged(Tracks tracks) {} /** - * Called when the combined {@link MediaMetadata} changes. - * - *

      The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata - * MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and - * any timed metadata that has been parsed from the media and output via {@link - * Listener#onMetadata(Metadata)}. If a field is populated in the {@link - * MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or - * timed metadata. + * Called when the value of {@link Player#getMediaMetadata()} changes. * *

      This method may be called multiple times in quick succession. * @@ -703,7 +698,7 @@ public interface Player { default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {} /** - * Called when the playlist {@link MediaMetadata} changes. + * Called when the value of {@link Player#getPlaylistMetadata()} changes. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -869,10 +864,10 @@ public interface Player { PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {} /** - * Called when the current playback parameters change. The playback parameters may change due to - * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change - * them (for example, if audio playback switches to passthrough or offload mode, where speed - * adjustment is no longer possible). + * Called when the value of {@link #getPlaybackParameters()} changes. The playback parameters + * may change due to a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player + * itself may change them (for example, if audio playback switches to passthrough or offload + * mode, where speed adjustment is no longer possible). * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -931,7 +926,7 @@ public interface Player { default void onAudioSessionIdChanged(int audioSessionId) {} /** - * Called when the audio attributes change. + * Called when the value of {@link #getAudioAttributes()} changes. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -941,7 +936,7 @@ public interface Player { default void onAudioAttributesChanged(AudioAttributes audioAttributes) {} /** - * Called when the volume changes. + * Called when the value of {@link #getVolume()} changes. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -971,7 +966,7 @@ public interface Player { default void onDeviceInfoChanged(DeviceInfo deviceInfo) {} /** - * Called when the device volume or mute state changes. + * Called when the value of {@link #getDeviceVolume()} or {@link #isDeviceMuted()} changes. * *

      {@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -1015,7 +1010,7 @@ public interface Player { default void onRenderedFirstFrame() {} /** - * Called when there is a change in the {@linkplain Cue cues}. + * Called when the value of {@link #getCurrentCues()} changes. * *

      Both this method and {@link #onCues(CueGroup)} are called when there is a change in the * cues. You should only implement one or the other. @@ -1029,7 +1024,7 @@ public interface Player { default void onCues(List cues) {} /** - * Called when there is a change in the {@link CueGroup}. + * Called when the value of {@link #getCurrentCues()} changes. * *

      Both this method and {@link #onCues(List)} are called when there is a change in the cues. * You should only implement one or the other. @@ -1379,7 +1374,7 @@ public interface Player { /** * Commands that indicate which method calls are currently permitted on a particular {@code - * Player} instance, and which corresponding {@link Player.Listener} methods will be invoked. + * Player} instance. * *

      The currently available commands can be inspected with {@link #getAvailableCommands()} and * {@link #isCommandAvailable(int)}. From 128fed81cab33f31dc10898a17475d07d9d82ee0 Mon Sep 17 00:00:00 2001 From: kimvde Date: Mon, 23 Jan 2023 09:28:57 +0000 Subject: [PATCH 089/104] Undo unreleased changes from transforming-media.md The "Transforming media" page has been updated with changes that won't be part of the next release. Undo these changes so that this page is consistent with the latest release. PiperOrigin-RevId: 503917637 (cherry picked from commit fdf866617f39261f6b619dd69f56279505ae7466) --- docs/transforming-media.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/transforming-media.md b/docs/transforming-media.md index 4ad82abeb4..3df1681c5e 100644 --- a/docs/transforming-media.md +++ b/docs/transforming-media.md @@ -70,8 +70,8 @@ Transformer.Listener transformerListener = } @Override - public void onTransformationError(MediaItem inputMediaItem, TransformationException e) { - displayError(e); + public void onTransformationError(MediaItem inputMediaItem, TransformationException exception) { + displayError(exception); } }; ~~~ From 92686b3377cb54bf096b7ea775f7b94a7c967d2d Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 24 Jan 2023 16:27:14 +0000 Subject: [PATCH 090/104] Minor fix in transforming-media.md PiperOrigin-RevId: 504281747 (cherry picked from commit 4c1be4c7a1eed2eec714cfaf854683c21dbfec93) From 26c8c552c059e354c87718cad99dd888941c8470 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 24 Jan 2023 18:04:27 +0000 Subject: [PATCH 091/104] Suppress warnings in ImaUtil ImaUtil calls VideoProgressUpdate.equals() which is annotated as hidden, which causes lint errors with gradle. #minor-release PiperOrigin-RevId: 504306210 (cherry picked from commit f86948f01c2397b9f53ef598e311e2de9055118e) --- .../main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index a9ae28d26c..a6c8d8430e 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -270,6 +270,7 @@ import java.util.Set; } /** Returns a human-readable representation of a video progress update. */ + @SuppressWarnings("RestrictedApi") // VideoProgressUpdate.equals() is annotated as hidden. public static String getStringForVideoProgressUpdate(VideoProgressUpdate videoProgressUpdate) { if (VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(videoProgressUpdate)) { return "not ready"; From 3bb3c602aef4e902dfd292d6b130dbdaa58a1ade Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 25 Jan 2023 11:41:28 +0000 Subject: [PATCH 092/104] Document two limitations with subtitle sideloading #minor-release PiperOrigin-RevId: 504517946 (cherry picked from commit f083ff264db8af76a1c9bb14bd72913b2fbb8fe2) --- docs/_includes/media3-known-issue-box.html | 2 ++ docs/media-items.md | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/_includes/media3-known-issue-box.html diff --git a/docs/_includes/media3-known-issue-box.html b/docs/_includes/media3-known-issue-box.html new file mode 100644 index 0000000000..93d2989c04 --- /dev/null +++ b/docs/_includes/media3-known-issue-box.html @@ -0,0 +1,2 @@ +**[Known issue #{{include.issue-id}}](https://github.com/androidx/media/issues/{{include.issue-id}})** - {{ include.description }} +{:.error} diff --git a/docs/media-items.md b/docs/media-items.md index 0ba89db3d1..2361f19a27 100644 --- a/docs/media-items.md +++ b/docs/media-items.md @@ -102,7 +102,8 @@ MediaItem mediaItem = Internally, `DefaultMediaSourceFactory` will use a `MergingMediaSource` to combine the content media source with a `SingleSampleMediaSource` for each -subtitle track. +subtitle track. `DefaultMediaSourceFactory` does not support sideloading +subtitles for multi-period DASH. ## Clipping a media stream ## @@ -152,5 +153,7 @@ Internally, `DefaultMediaSourceFactory` will wrap the content media source in an the player also needs to have its `DefaultMediaSourceFactory` [configured accordingly]({{ site.baseurl }}/ad-insertion.html#declarative-ad-support). +{% include media3-known-issue-box.html issue-id="185" description="Subtitles, clipping and ad insertion are only supported if you use `DefaultMediaSourceFactory`." %} + [playlist API]: {{ site.baseurl }}/playlists.html [`MediaItem.Builder` Javadoc]: {{ site.exo_sdk }}/MediaItem.Builder.html From 5350d0666eb252aacdc8b7ac097cafe7cdb81100 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 25 Jan 2023 17:56:13 +0000 Subject: [PATCH 093/104] Add missing command checks in UI module The commands are partly checked already before enabling features or calling player methods, but the checks were still missing in many places. #minor-release PiperOrigin-RevId: 504589888 (cherry picked from commit 2d7ddccebb959051ae4a284e9c9380cc3582c411) --- .../ui/DefaultMediaDescriptionAdapter.java | 11 ++ .../ui/PlayerNotificationManager.java | 51 +++++-- .../ui/StyledPlayerControlView.java | 137 ++++++++++++------ .../exoplayer2/ui/StyledPlayerView.java | 46 ++++-- .../ui/TrackSelectionDialogBuilder.java | 10 +- .../DefaultMediaDescriptionAdapterTest.java | 21 ++- 6 files changed, 208 insertions(+), 68 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapter.java index 629a8c0621..5b727efd9a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapter.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; + import android.app.PendingIntent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -46,6 +48,9 @@ public final class DefaultMediaDescriptionAdapter implements MediaDescriptionAda @Override public CharSequence getCurrentContentTitle(Player player) { + if (!player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)) { + return ""; + } @Nullable CharSequence displayTitle = player.getMediaMetadata().displayTitle; if (!TextUtils.isEmpty(displayTitle)) { return displayTitle; @@ -64,6 +69,9 @@ public final class DefaultMediaDescriptionAdapter implements MediaDescriptionAda @Nullable @Override public CharSequence getCurrentContentText(Player player) { + if (!player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)) { + return null; + } @Nullable CharSequence artist = player.getMediaMetadata().artist; if (!TextUtils.isEmpty(artist)) { return artist; @@ -75,6 +83,9 @@ public final class DefaultMediaDescriptionAdapter implements MediaDescriptionAda @Nullable @Override public Bitmap getCurrentLargeIcon(Player player, BitmapCallback callback) { + if (!player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)) { + return null; + } @Nullable byte[] data = player.getMediaMetadata().artworkData; if (data == null) { return null; 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 a129954bc4..6e0537d79f 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 @@ -15,10 +15,17 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; +import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; +import static com.google.android.exoplayer2.Player.COMMAND_PREPARE; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_BACK; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS; +import static com.google.android.exoplayer2.Player.COMMAND_STOP; import static com.google.android.exoplayer2.Player.EVENT_IS_PLAYING_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_MEDIA_METADATA_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED; @@ -1203,7 +1210,9 @@ public class PlayerNotificationManager { @Nullable NotificationCompat.Builder builder, boolean ongoing, @Nullable Bitmap largeIcon) { - if (player.getPlaybackState() == Player.STATE_IDLE && player.getCurrentTimeline().isEmpty()) { + if (player.getPlaybackState() == Player.STATE_IDLE + && player.isCommandAvailable(COMMAND_GET_TIMELINE) + && player.getCurrentTimeline().isEmpty()) { builderActions = null; return null; } @@ -1257,6 +1266,7 @@ public class PlayerNotificationManager { // Changing "showWhen" causes notification flicker if SDK_INT < 21. if (Util.SDK_INT >= 21 && useChronometer + && player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) && player.isPlaying() && !player.isPlayingAd() && !player.isCurrentMediaItemDynamic() @@ -1535,24 +1545,43 @@ public class PlayerNotificationManager { } String action = intent.getAction(); if (ACTION_PLAY.equals(action)) { - if (player.getPlaybackState() == Player.STATE_IDLE) { + if (player.getPlaybackState() == Player.STATE_IDLE + && player.isCommandAvailable(COMMAND_PREPARE)) { player.prepare(); - } else if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(player.getCurrentMediaItemIndex()); + } else if (player.getPlaybackState() == Player.STATE_ENDED + && player.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)) { + player.seekToDefaultPosition(); + } + if (player.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + player.play(); } - player.play(); } else if (ACTION_PAUSE.equals(action)) { - player.pause(); + if (player.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + player.pause(); + } } else if (ACTION_PREVIOUS.equals(action)) { - player.seekToPrevious(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)) { + player.seekToPrevious(); + } } else if (ACTION_REWIND.equals(action)) { - player.seekBack(); + if (player.isCommandAvailable(COMMAND_SEEK_BACK)) { + player.seekBack(); + } } else if (ACTION_FAST_FORWARD.equals(action)) { - player.seekForward(); + if (player.isCommandAvailable(COMMAND_SEEK_FORWARD)) { + player.seekForward(); + } } else if (ACTION_NEXT.equals(action)) { - player.seekToNext(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)) { + player.seekToNext(); + } } else if (ACTION_STOP.equals(action)) { - player.stop(/* reset= */ true); + if (player.isCommandAvailable(COMMAND_STOP)) { + player.stop(); + } + if (player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)) { + player.clearMediaItems(); + } } else if (ACTION_DISMISS.equals(action)) { stopNotification(/* dismissedByUser= */ true); } else if (action != null diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index 55e9680b06..958c961821 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -15,11 +15,22 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACKS; +import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; +import static com.google.android.exoplayer2.Player.COMMAND_PREPARE; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_BACK; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS; +import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH; +import static com.google.android.exoplayer2.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; import static com.google.android.exoplayer2.Player.EVENT_AVAILABLE_COMMANDS_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_IS_PLAYING_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED; @@ -35,6 +46,7 @@ import static com.google.android.exoplayer2.Player.EVENT_TRACKS_CHANGED; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.getDrawable; +import static com.google.android.exoplayer2.util.Util.msToUs; import android.annotation.SuppressLint; import android.content.Context; @@ -804,7 +816,7 @@ public class StyledPlayerControlView extends FrameLayout { */ public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { this.repeatToggleModes = repeatToggleModes; - if (player != null) { + if (player != null && player.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { @Player.RepeatMode int currentMode = player.getRepeatMode(); if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE && currentMode != Player.REPEAT_MODE_OFF) { @@ -1068,7 +1080,7 @@ public class StyledPlayerControlView extends FrameLayout { } @Nullable Player player = this.player; - if (player == null) { + if (player == null || !player.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { updateButton(/* enabled= */ false, repeatToggleButton); repeatToggleButton.setImageDrawable(repeatOffButtonDrawable); repeatToggleButton.setContentDescription(repeatOffButtonContentDescription); @@ -1102,7 +1114,7 @@ public class StyledPlayerControlView extends FrameLayout { @Nullable Player player = this.player; if (!controlViewLayoutManager.getShowButton(shuffleButton)) { updateButton(/* enabled= */ false, shuffleButton); - } else if (player == null) { + } else if (player == null || !player.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { updateButton(/* enabled= */ false, shuffleButton); shuffleButton.setImageDrawable(shuffleOffButtonDrawable); shuffleButton.setContentDescription(shuffleOffContentDescription); @@ -1126,8 +1138,8 @@ public class StyledPlayerControlView extends FrameLayout { textTrackSelectionAdapter.clear(); audioTrackSelectionAdapter.clear(); if (player == null - || !player.isCommandAvailable(Player.COMMAND_GET_TRACKS) - || !player.isCommandAvailable(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { + || !player.isCommandAvailable(COMMAND_GET_TRACKS) + || !player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { return; } Tracks tracks = player.getCurrentTracks(); @@ -1168,12 +1180,14 @@ public class StyledPlayerControlView extends FrameLayout { if (player == null) { return; } - multiWindowTimeBar = - showMultiWindowTimeBar && canShowMultiWindowTimeBar(player.getCurrentTimeline(), window); + multiWindowTimeBar = showMultiWindowTimeBar && canShowMultiWindowTimeBar(player, window); currentWindowOffset = 0; long durationUs = 0; int adGroupCount = 0; - Timeline timeline = player.getCurrentTimeline(); + Timeline timeline = + player.isCommandAvailable(COMMAND_GET_TIMELINE) + ? player.getCurrentTimeline() + : Timeline.EMPTY; if (!timeline.isEmpty()) { int currentWindowIndex = player.getCurrentMediaItemIndex(); int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; @@ -1215,6 +1229,11 @@ public class StyledPlayerControlView extends FrameLayout { } durationUs += window.durationUs; } + } else if (player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { + long playerDurationMs = player.getContentDuration(); + if (playerDurationMs != C.TIME_UNSET) { + durationUs = msToUs(playerDurationMs); + } } long durationMs = Util.usToMs(durationUs); if (durationView != null) { @@ -1242,7 +1261,7 @@ public class StyledPlayerControlView extends FrameLayout { @Nullable Player player = this.player; long position = 0; long bufferedPosition = 0; - if (player != null) { + if (player != null && player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { position = currentWindowOffset + player.getContentPosition(); bufferedPosition = currentWindowOffset + player.getContentBufferedPosition(); } @@ -1320,7 +1339,7 @@ public class StyledPlayerControlView extends FrameLayout { } private void setPlaybackSpeed(float speed) { - if (player == null) { + if (player == null || !player.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { return; } player.setPlaybackParameters(player.getPlaybackParameters().withSpeed(speed)); @@ -1341,11 +1360,12 @@ public class StyledPlayerControlView extends FrameLayout { } private void seekToTimeBarPosition(Player player, long positionMs) { - int windowIndex; - Timeline timeline = player.getCurrentTimeline(); - if (multiWindowTimeBar && !timeline.isEmpty()) { + if (multiWindowTimeBar + && player.isCommandAvailable(COMMAND_GET_TIMELINE) + && player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + Timeline timeline = player.getCurrentTimeline(); int windowCount = timeline.getWindowCount(); - windowIndex = 0; + int windowIndex = 0; while (true) { long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs(); if (positionMs < windowDurationMs) { @@ -1358,17 +1378,13 @@ public class StyledPlayerControlView extends FrameLayout { positionMs -= windowDurationMs; windowIndex++; } - } else { - windowIndex = player.getCurrentMediaItemIndex(); + player.seekTo(windowIndex, positionMs); + } else if (player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { + player.seekTo(positionMs); } - seekTo(player, windowIndex, positionMs); updateProgress(); } - private void seekTo(Player player, int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); - } - private void onFullScreenButtonClicked(View v) { if (onFullScreenModeChangedListener == null) { return; @@ -1446,10 +1462,12 @@ public class StyledPlayerControlView extends FrameLayout { } if (event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { - if (player.getPlaybackState() != Player.STATE_ENDED) { + if (player.getPlaybackState() != Player.STATE_ENDED + && player.isCommandAvailable(COMMAND_SEEK_FORWARD)) { player.seekForward(); } - } else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) { + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND + && player.isCommandAvailable(COMMAND_SEEK_BACK)) { player.seekBack(); } else if (event.getRepeatCount() == 0) { switch (keyCode) { @@ -1464,10 +1482,14 @@ public class StyledPlayerControlView extends FrameLayout { dispatchPause(player); break; case KeyEvent.KEYCODE_MEDIA_NEXT: - player.seekToNext(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)) { + player.seekToNext(); + } break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - player.seekToPrevious(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)) { + player.seekToPrevious(); + } break; default: break; @@ -1507,7 +1529,10 @@ public class StyledPlayerControlView extends FrameLayout { } private boolean shouldEnablePlayPauseButton() { - return player != null && !player.getCurrentTimeline().isEmpty(); + return player != null + && player.isCommandAvailable(COMMAND_PLAY_PAUSE) + && (!player.isCommandAvailable(COMMAND_GET_TIMELINE) + || !player.getCurrentTimeline().isEmpty()); } private boolean shouldShowPauseButton() { @@ -1528,16 +1553,21 @@ public class StyledPlayerControlView extends FrameLayout { private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); - if (state == Player.STATE_IDLE) { + if (state == Player.STATE_IDLE && player.isCommandAvailable(COMMAND_PREPARE)) { player.prepare(); - } else if (state == Player.STATE_ENDED) { - seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); + } else if (state == Player.STATE_ENDED + && player.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)) { + player.seekToDefaultPosition(); + } + if (player.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + player.play(); } - player.play(); } private void dispatchPause(Player player) { - player.pause(); + if (player.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + player.pause(); + } } @SuppressLint("InlinedApi") @@ -1553,13 +1583,18 @@ public class StyledPlayerControlView extends FrameLayout { } /** - * Returns whether the specified {@code timeline} can be shown on a multi-window time bar. + * Returns whether the specified {@code player} can be shown on a multi-window time bar. * - * @param timeline The {@link Timeline} to check. + * @param player The {@link Player} to check. * @param window A scratch {@link Timeline.Window} instance. * @return Whether the specified timeline can be shown on a multi-window time bar. */ - private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) { + private static boolean canShowMultiWindowTimeBar(Player player, Timeline.Window window) { + if (!player.isCommandAvailable(COMMAND_GET_TIMELINE) + || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + return false; + } + Timeline timeline = player.getCurrentTimeline(); if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { return false; } @@ -1680,22 +1715,33 @@ public class StyledPlayerControlView extends FrameLayout { } controlViewLayoutManager.resetHideCallbacks(); if (nextButton == view) { - player.seekToNext(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)) { + player.seekToNext(); + } } else if (previousButton == view) { - player.seekToPrevious(); + if (player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)) { + player.seekToPrevious(); + } } else if (fastForwardButton == view) { - if (player.getPlaybackState() != Player.STATE_ENDED) { + if (player.getPlaybackState() != Player.STATE_ENDED + && player.isCommandAvailable(COMMAND_SEEK_FORWARD)) { player.seekForward(); } } else if (rewindButton == view) { - player.seekBack(); + if (player.isCommandAvailable(COMMAND_SEEK_BACK)) { + player.seekBack(); + } } else if (playPauseButton == view) { dispatchPlayPause(player); } else if (repeatToggleButton == view) { - player.setRepeatMode( - RepeatModeUtil.getNextRepeatMode(player.getRepeatMode(), repeatToggleModes)); + if (player.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { + player.setRepeatMode( + RepeatModeUtil.getNextRepeatMode(player.getRepeatMode(), repeatToggleModes)); + } } else if (shuffleButton == view) { - player.setShuffleModeEnabled(!player.getShuffleModeEnabled()); + if (player.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { + player.setShuffleModeEnabled(!player.getShuffleModeEnabled()); + } } else if (settingsButton == view) { controlViewLayoutManager.removeHideCallbacks(); displaySettingsWindow(settingsAdapter, settingsButton); @@ -1898,7 +1944,8 @@ public class StyledPlayerControlView extends FrameLayout { holder.checkView.setVisibility(isTrackSelectionOff ? VISIBLE : INVISIBLE); holder.itemView.setOnClickListener( v -> { - if (player != null) { + if (player != null + && player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); player.setTrackSelectionParameters( @@ -1939,7 +1986,8 @@ public class StyledPlayerControlView extends FrameLayout { holder.checkView.setVisibility(hasSelectionOverride ? INVISIBLE : VISIBLE); holder.itemView.setOnClickListener( v -> { - if (player == null) { + if (player == null + || !player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { return; } TrackSelectionParameters trackSelectionParameters = @@ -2042,6 +2090,9 @@ public class StyledPlayerControlView extends FrameLayout { holder.checkView.setVisibility(explicitlySelected ? VISIBLE : INVISIBLE); holder.itemView.setOnClickListener( v -> { + if (!player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { + return; + } TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); player.setTrackSelectionParameters( diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 48e6821f52..5aaae64efe 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -15,7 +15,11 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACKS; import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.getDrawable; @@ -529,10 +533,12 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { @Nullable Player oldPlayer = this.player; if (oldPlayer != null) { oldPlayer.removeListener(componentListener); - if (surfaceView instanceof TextureView) { - oldPlayer.clearVideoTextureView((TextureView) surfaceView); - } else if (surfaceView instanceof SurfaceView) { - oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView); + if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) { + if (surfaceView instanceof TextureView) { + oldPlayer.clearVideoTextureView((TextureView) surfaceView); + } else if (surfaceView instanceof SurfaceView) { + oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView); + } } } if (subtitleView != null) { @@ -735,7 +741,9 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (player != null && player.isPlayingAd()) { + if (player != null + && player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) + && player.isPlayingAd()) { return super.dispatchKeyEvent(event); } @@ -1239,7 +1247,8 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { } int playbackState = player.getPlaybackState(); return controllerAutoShow - && !player.getCurrentTimeline().isEmpty() + && (!player.isCommandAvailable(COMMAND_GET_TIMELINE) + || !player.getCurrentTimeline().isEmpty()) && (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED || !checkNotNull(player).getPlayWhenReady()); @@ -1254,12 +1263,17 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { } private boolean isPlayingAd() { - return player != null && player.isPlayingAd() && player.getPlayWhenReady(); + return player != null + && player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) + && player.isPlayingAd() + && player.getPlayWhenReady(); } private void updateForCurrentTrackSelections(boolean isNewPlayer) { @Nullable Player player = this.player; - if (player == null || player.getCurrentTracks().isEmpty()) { + if (player == null + || !player.isCommandAvailable(COMMAND_GET_TRACKS) + || player.getCurrentTracks().isEmpty()) { if (!keepContentOnPlayerReset) { hideArtwork(); closeShutter(); @@ -1283,7 +1297,7 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { closeShutter(); // Display artwork if enabled and available, else hide it. if (useArtwork()) { - if (setArtworkFromMediaMetadata(player.getMediaMetadata())) { + if (setArtworkFromMediaMetadata(player)) { return; } if (setDrawableArtwork(defaultArtwork)) { @@ -1295,7 +1309,11 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { } @RequiresNonNull("artworkView") - private boolean setArtworkFromMediaMetadata(MediaMetadata mediaMetadata) { + private boolean setArtworkFromMediaMetadata(Player player) { + if (!player.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)) { + return false; + } + MediaMetadata mediaMetadata = player.getMediaMetadata(); if (mediaMetadata.artworkData == null) { return false; } @@ -1514,10 +1532,14 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { // is necessary to avoid closing the shutter when such a transition occurs. See: // https://github.com/google/ExoPlayer/issues/5507. Player player = checkNotNull(StyledPlayerView.this.player); - Timeline timeline = player.getCurrentTimeline(); + Timeline timeline = + player.isCommandAvailable(COMMAND_GET_TIMELINE) + ? player.getCurrentTimeline() + : Timeline.EMPTY; if (timeline.isEmpty()) { lastPeriodUidWithTracks = null; - } else if (!player.getCurrentTracks().isEmpty()) { + } else if (player.isCommandAvailable(COMMAND_GET_TRACKS) + && !player.getCurrentTracks().isEmpty()) { lastPeriodUidWithTracks = timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid; } else if (lastPeriodUidWithTracks != null) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java index 29ca89d419..e613b42d1e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java @@ -15,6 +15,9 @@ */ package com.google.android.exoplayer2.ui; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACKS; +import static com.google.android.exoplayer2.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; + import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; @@ -100,7 +103,9 @@ public final class TrackSelectionDialogBuilder { Context context, CharSequence title, Player player, @C.TrackType int trackType) { this.context = context; this.title = title; - List allTrackGroups = player.getCurrentTracks().getGroups(); + Tracks tracks = + player.isCommandAvailable(COMMAND_GET_TRACKS) ? player.getCurrentTracks() : Tracks.EMPTY; + List allTrackGroups = tracks.getGroups(); trackGroups = new ArrayList<>(); for (int i = 0; i < allTrackGroups.size(); i++) { Tracks.Group trackGroup = allTrackGroups.get(i); @@ -111,6 +116,9 @@ public final class TrackSelectionDialogBuilder { overrides = player.getTrackSelectionParameters().overrides; callback = (isDisabled, overrides) -> { + if (!player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { + return; + } TrackSelectionParameters.Builder parametersBuilder = player.getTrackSelectionParameters().buildUpon(); parametersBuilder.setTrackTypeDisabled(trackType, isDisabled); diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapterTest.java b/library/ui/src/test/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapterTest.java index 4ebe0f42e5..eb21c959f1 100644 --- a/library/ui/src/test/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapterTest.java +++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/DefaultMediaDescriptionAdapterTest.java @@ -34,7 +34,7 @@ import org.junit.runner.RunWith; public class DefaultMediaDescriptionAdapterTest { @Test - public void getters_returnMediaMetadataValues() { + public void getters_withGetMetatadataCommandAvailable_returnMediaMetadataValues() { Context context = ApplicationProvider.getApplicationContext(); Player player = mock(Player.class); MediaMetadata mediaMetadata = @@ -43,6 +43,7 @@ public class DefaultMediaDescriptionAdapterTest { PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); DefaultMediaDescriptionAdapter adapter = new DefaultMediaDescriptionAdapter(pendingIntent); + when(player.isCommandAvailable(Player.COMMAND_GET_MEDIA_ITEMS_METADATA)).thenReturn(true); when(player.getMediaMetadata()).thenReturn(mediaMetadata); assertThat(adapter.createCurrentContentIntent(player)).isEqualTo(pendingIntent); @@ -51,4 +52,22 @@ public class DefaultMediaDescriptionAdapterTest { assertThat(adapter.getCurrentContentText(player).toString()) .isEqualTo(mediaMetadata.artist.toString()); } + + @Test + public void getters_withoutGetMetatadataCommandAvailable_returnMediaMetadataValues() { + Context context = ApplicationProvider.getApplicationContext(); + Player player = mock(Player.class); + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setDisplayTitle("display title").setArtist("artist").build(); + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); + DefaultMediaDescriptionAdapter adapter = new DefaultMediaDescriptionAdapter(pendingIntent); + + when(player.isCommandAvailable(Player.COMMAND_GET_MEDIA_ITEMS_METADATA)).thenReturn(false); + when(player.getMediaMetadata()).thenReturn(mediaMetadata); + + assertThat(adapter.createCurrentContentIntent(player)).isEqualTo(pendingIntent); + assertThat(adapter.getCurrentContentTitle(player).toString()).isEqualTo(""); + assertThat(adapter.getCurrentContentText(player)).isNull(); + } } From b9bb3235c2f64f6e7597baa74e72b371310ab751 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 27 Jan 2023 09:50:24 +0000 Subject: [PATCH 094/104] Tweak UI behavior when commands are missing. For most missing commands, we already disable the corresponding controls. This change extends this to more UI elements that are disabled in case the corresponding action is unavailable. #minor-release PiperOrigin-RevId: 505057751 (cherry picked from commit 641c3b1b22cc67133151b7af9905473485b0f51c) --- .../ui/StyledPlayerControlView.java | 103 +++++++++++++----- 1 file changed, 74 insertions(+), 29 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index 958c961821..e7e7e16616 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -1016,7 +1016,10 @@ public class StyledPlayerControlView extends FrameLayout { boolean enableFastForward = false; boolean enableNext = false; if (player != null) { - enableSeeking = player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); + enableSeeking = + (showMultiWindowTimeBar && canShowMultiWindowTimeBar(player, window)) + ? player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM) + : player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM); enablePrevious = player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS); enableRewind = player.isCommandAvailable(COMMAND_SEEK_BACK); enableFastForward = player.isCommandAvailable(COMMAND_SEEK_FORWARD); @@ -1132,6 +1135,7 @@ public class StyledPlayerControlView extends FrameLayout { private void updateTrackLists() { initTrackSelectionAdapter(); updateButton(textTrackSelectionAdapter.getItemCount() > 0, subtitleButton); + updateSettingsButton(); } private void initTrackSelectionAdapter() { @@ -1307,6 +1311,11 @@ public class StyledPlayerControlView extends FrameLayout { playbackSpeedAdapter.updateSelectedIndex(player.getPlaybackParameters().speed); settingsAdapter.setSubTextAtPosition( SETTINGS_PLAYBACK_SPEED_POSITION, playbackSpeedAdapter.getSelectedText()); + updateSettingsButton(); + } + + private void updateSettingsButton() { + updateButton(settingsAdapter.hasSettingsToShow(), settingsButton); } private void updateSettingsWindowSize() { @@ -1360,25 +1369,26 @@ public class StyledPlayerControlView extends FrameLayout { } private void seekToTimeBarPosition(Player player, long positionMs) { - if (multiWindowTimeBar - && player.isCommandAvailable(COMMAND_GET_TIMELINE) - && player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { - Timeline timeline = player.getCurrentTimeline(); - int windowCount = timeline.getWindowCount(); - int windowIndex = 0; - while (true) { - long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs(); - if (positionMs < windowDurationMs) { - break; - } else if (windowIndex == windowCount - 1) { - // Seeking past the end of the last window should seek to the end of the timeline. - positionMs = windowDurationMs; - break; + if (multiWindowTimeBar) { + if (player.isCommandAvailable(COMMAND_GET_TIMELINE) + && player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + Timeline timeline = player.getCurrentTimeline(); + int windowCount = timeline.getWindowCount(); + int windowIndex = 0; + while (true) { + long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs(); + if (positionMs < windowDurationMs) { + break; + } else if (windowIndex == windowCount - 1) { + // Seeking past the end of the last window should seek to the end of the timeline. + positionMs = windowDurationMs; + break; + } + positionMs -= windowDurationMs; + windowIndex++; } - positionMs -= windowDurationMs; - windowIndex++; + player.seekTo(windowIndex, positionMs); } - player.seekTo(windowIndex, positionMs); } else if (player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { player.seekTo(positionMs); } @@ -1590,15 +1600,14 @@ public class StyledPlayerControlView extends FrameLayout { * @return Whether the specified timeline can be shown on a multi-window time bar. */ private static boolean canShowMultiWindowTimeBar(Player player, Timeline.Window window) { - if (!player.isCommandAvailable(COMMAND_GET_TIMELINE) - || !player.isCommandAvailable(COMMAND_SEEK_TO_MEDIA_ITEM)) { + if (!player.isCommandAvailable(COMMAND_GET_TIMELINE)) { return false; } Timeline timeline = player.getCurrentTimeline(); - if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { + int windowCount = timeline.getWindowCount(); + if (windowCount <= 1 || windowCount > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { return false; } - int windowCount = timeline.getWindowCount(); for (int i = 0; i < windowCount; i++) { if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) { return false; @@ -1641,17 +1650,24 @@ public class StyledPlayerControlView extends FrameLayout { @Override public void onEvents(Player player, Events events) { - if (events.containsAny(EVENT_PLAYBACK_STATE_CHANGED, EVENT_PLAY_WHEN_READY_CHANGED)) { + if (events.containsAny( + EVENT_PLAYBACK_STATE_CHANGED, + EVENT_PLAY_WHEN_READY_CHANGED, + EVENT_AVAILABLE_COMMANDS_CHANGED)) { updatePlayPauseButton(); } if (events.containsAny( - EVENT_PLAYBACK_STATE_CHANGED, EVENT_PLAY_WHEN_READY_CHANGED, EVENT_IS_PLAYING_CHANGED)) { + EVENT_PLAYBACK_STATE_CHANGED, + EVENT_PLAY_WHEN_READY_CHANGED, + EVENT_IS_PLAYING_CHANGED, + EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateProgress(); } - if (events.contains(EVENT_REPEAT_MODE_CHANGED)) { + if (events.containsAny(EVENT_REPEAT_MODE_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateRepeatModeButton(); } - if (events.contains(EVENT_SHUFFLE_MODE_ENABLED_CHANGED)) { + if (events.containsAny( + EVENT_SHUFFLE_MODE_ENABLED_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateShuffleButton(); } if (events.containsAny( @@ -1664,13 +1680,14 @@ public class StyledPlayerControlView extends FrameLayout { EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateNavigation(); } - if (events.containsAny(EVENT_POSITION_DISCONTINUITY, EVENT_TIMELINE_CHANGED)) { + if (events.containsAny( + EVENT_POSITION_DISCONTINUITY, EVENT_TIMELINE_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateTimeline(); } - if (events.contains(EVENT_PLAYBACK_PARAMETERS_CHANGED)) { + if (events.containsAny(EVENT_PLAYBACK_PARAMETERS_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { updatePlaybackSpeedList(); } - if (events.contains(EVENT_TRACKS_CHANGED)) { + if (events.containsAny(EVENT_TRACKS_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { updateTrackLists(); } } @@ -1780,6 +1797,14 @@ public class StyledPlayerControlView extends FrameLayout { @Override public void onBindViewHolder(SettingViewHolder holder, int position) { + if (shouldShowSetting(position)) { + holder.itemView.setLayoutParams( + new RecyclerView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } else { + holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(0, 0)); + } + holder.mainTextView.setText(mainTexts[position]); if (subTexts[position] == null) { @@ -1808,6 +1833,26 @@ public class StyledPlayerControlView extends FrameLayout { public void setSubTextAtPosition(int position, String subText) { this.subTexts[position] = subText; } + + public boolean hasSettingsToShow() { + return shouldShowSetting(SETTINGS_AUDIO_TRACK_SELECTION_POSITION) + || shouldShowSetting(SETTINGS_PLAYBACK_SPEED_POSITION); + } + + private boolean shouldShowSetting(int position) { + if (player == null) { + return false; + } + switch (position) { + case SETTINGS_AUDIO_TRACK_SELECTION_POSITION: + return player.isCommandAvailable(COMMAND_GET_TRACKS) + && player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS); + case SETTINGS_PLAYBACK_SPEED_POSITION: + return player.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH); + default: + return true; + } + } } private final class SettingViewHolder extends RecyclerView.ViewHolder { From ace97facf2c0fea5edc14858a36f0099724ce61f Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Fri, 27 Jan 2023 11:25:33 +0000 Subject: [PATCH 095/104] Match MergingMediaPeriod track selection by period index in id MergingMediaPeriod creates its track groups with ids concatenating position in its periods array and the underlying child track group id. The ids can be used in selectTracks for matching to periods list. Issue: google/ExoPlayer#10930 PiperOrigin-RevId: 505074653 (cherry picked from commit ee055ef004686ddb3844666f9506a95c6e16a59f) --- .../exoplayer2/source/MergingMediaPeriod.java | 14 +++----- .../source/MergingMediaPeriodTest.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 09eec4815a..8f056fc094 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -117,17 +117,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; for (int i = 0; i < selections.length; i++) { Integer streamChildIndex = streams[i] == null ? null : streamPeriodIndices.get(streams[i]); streamChildIndices[i] = streamChildIndex == null ? C.INDEX_UNSET : streamChildIndex; - selectionChildIndices[i] = C.INDEX_UNSET; if (selections[i] != null) { TrackGroup mergedTrackGroup = selections[i].getTrackGroup(); - TrackGroup childTrackGroup = - checkNotNull(childTrackGroupByMergedTrackGroup.get(mergedTrackGroup)); - for (int j = 0; j < periods.length; j++) { - if (periods[j].getTrackGroups().indexOf(childTrackGroup) != C.INDEX_UNSET) { - selectionChildIndices[i] = j; - break; - } - } + // mergedTrackGroup.id is 'periods array index' + ":" + childTrackGroup.id + selectionChildIndices[i] = + Integer.parseInt(mergedTrackGroup.id.substring(0, mergedTrackGroup.id.indexOf(":"))); + } else { + selectionChildIndices[i] = C.INDEX_UNSET; } } streamPeriodIndices.clear(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java index ee5425c7b9..be0d8848d7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java @@ -197,6 +197,39 @@ public final class MergingMediaPeriodTest { assertThat(firstSelectionChild2).isEqualTo(secondSelectionChild2); } + // https://github.com/google/ExoPlayer/issues/10930 + @Test + public void selectTracks_withIdenticalFormats_selectsMatchingPeriod() throws Exception { + MergingMediaPeriod mergingMediaPeriod = + prepareMergingPeriod( + new MergingPeriodDefinition( + /* timeOffsetUs= */ 0, /* singleSampleTimeUs= */ 123_000, childFormat11), + new MergingPeriodDefinition( + /* timeOffsetUs= */ -3000, /* singleSampleTimeUs= */ 456_000, childFormat11)); + + ExoTrackSelection[] selectionArray = { + new FixedTrackSelection(mergingMediaPeriod.getTrackGroups().get(1), /* track= */ 0) + }; + + SampleStream[] streams = new SampleStream[1]; + mergingMediaPeriod.selectTracks( + selectionArray, + /* mayRetainStreamFlags= */ new boolean[2], + streams, + /* streamResetFlags= */ new boolean[2], + /* positionUs= */ 0); + mergingMediaPeriod.continueLoading(/* positionUs= */ 0); + + FormatHolder formatHolder = new FormatHolder(); + DecoderInputBuffer inputBuffer = + new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + streams[0].readData(formatHolder, inputBuffer, FLAG_REQUIRE_FORMAT); + + assertThat(streams[0].readData(formatHolder, inputBuffer, /* readFlags= */ 0)) + .isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.timeUs).isEqualTo(456_000 - 3000); + } + private MergingMediaPeriod prepareMergingPeriod(MergingPeriodDefinition... definitions) throws Exception { MediaPeriod[] mediaPeriods = new MediaPeriod[definitions.length]; From 284bf970072655a8ba1a905f037586fa32f74139 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 27 Jan 2023 14:01:47 +0000 Subject: [PATCH 096/104] Fix timestamp comparison for seeks in fMP4 When seeking in fMP4, we try to extract as little samples as possible by only starting at the preceding sync frame. This comparison should use <= to allow sync frames at exactly the seek position. Issue: google/ExoPlayer#10941 #minor-release PiperOrigin-RevId: 505098172 (cherry picked from commit ac3017b5805cb98b4292e4bbe1f7181f47684b88) --- .../extractor/mp4/FragmentedMp4Extractor.java | 6 +- .../mp4/sample_ac3_fragmented.mp4.1.dump | 18 ++--- .../mp4/sample_ac3_fragmented.mp4.2.dump | 12 +-- .../mp4/sample_eac3_fragmented.mp4.1.dump | 78 +++++++++---------- .../mp4/sample_eac3_fragmented.mp4.2.dump | 42 +++++----- 5 files changed, 70 insertions(+), 86 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 52dff7dfc3..c918fc5790 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -1673,15 +1673,15 @@ public class FragmentedMp4Extractor implements Extractor { } /** - * Advances {@link #firstSampleToOutputIndex} to point to the sync sample before the specified - * seek time in the current fragment. + * Advances {@link #firstSampleToOutputIndex} to point to the sync sample at or before the + * specified seek time in the current fragment. * * @param timeUs The seek time, in microseconds. */ public void seek(long timeUs) { int searchIndex = currentSampleIndex; while (searchIndex < fragment.sampleCount - && fragment.getSamplePresentationTimeUs(searchIndex) < timeUs) { + && fragment.getSamplePresentationTimeUs(searchIndex) <= timeUs) { if (fragment.sampleIsSyncFrameTable[searchIndex]) { firstSampleToOutputIndex = searchIndex; } diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump index 0f902e441a..4bcb712c34 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.1.dump @@ -7,8 +7,8 @@ seekMap: getPosition(288000) = [[timeUs=0, position=636]] numberOfTracks = 1 track 0: - total output bytes = 10752 - sample count = 7 + total output bytes = 9216 + sample count = 6 format 0: averageBitrate = 384000 peakBitrate = 384000 @@ -18,30 +18,26 @@ track 0: sampleRate = 48000 language = und sample 0: - time = 64000 - flags = 1 - data = length 1536, hash 5D09685 - sample 1: time = 96000 flags = 1 data = length 1536, hash A9A24E44 - sample 2: + sample 1: time = 128000 flags = 1 data = length 1536, hash 6F856273 - sample 3: + sample 2: time = 160000 flags = 1 data = length 1536, hash B1737D3C - sample 4: + sample 3: time = 192000 flags = 1 data = length 1536, hash 98FDEB9D - sample 5: + sample 4: time = 224000 flags = 1 data = length 1536, hash 99B9B943 - sample 6: + sample 5: time = 256000 flags = 1 data = length 1536, hash AAD9FCD2 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump index d747be40c5..c03c03e6c0 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_ac3_fragmented.mp4.2.dump @@ -7,8 +7,8 @@ seekMap: getPosition(288000) = [[timeUs=0, position=636]] numberOfTracks = 1 track 0: - total output bytes = 6144 - sample count = 4 + total output bytes = 4608 + sample count = 3 format 0: averageBitrate = 384000 peakBitrate = 384000 @@ -18,18 +18,14 @@ track 0: sampleRate = 48000 language = und sample 0: - time = 160000 - flags = 1 - data = length 1536, hash B1737D3C - sample 1: time = 192000 flags = 1 data = length 1536, hash 98FDEB9D - sample 2: + sample 1: time = 224000 flags = 1 data = length 1536, hash 99B9B943 - sample 3: + sample 2: time = 256000 flags = 1 data = length 1536, hash AAD9FCD2 diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index 027e7eb633..e33b92c7bc 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -7,8 +7,8 @@ seekMap: getPosition(1728000) = [[timeUs=0, position=638]] numberOfTracks = 1 track 0: - total output bytes = 148000 - sample count = 37 + total output bytes = 144000 + sample count = 36 format 0: peakBitrate = 1000000 id = 1 @@ -17,150 +17,146 @@ track 0: sampleRate = 48000 language = und sample 0: - time = 544000 - flags = 1 - data = length 4000, hash 27F20D29 - sample 1: time = 576000 flags = 1 data = length 4000, hash 6F565894 - sample 2: + sample 1: time = 608000 flags = 1 data = length 4000, hash A6F07C4A - sample 3: + sample 2: time = 640000 flags = 1 data = length 4000, hash 3A0CA15C - sample 4: + sample 3: time = 672000 flags = 1 data = length 4000, hash DB365414 - sample 5: + sample 4: time = 704000 flags = 1 data = length 4000, hash 31E08469 - sample 6: + sample 5: time = 736000 flags = 1 data = length 4000, hash 315F5C28 - sample 7: + sample 6: time = 768000 flags = 1 data = length 4000, hash CC65DF80 - sample 8: + sample 7: time = 800000 flags = 1 data = length 4000, hash 503FB64C - sample 9: + sample 8: time = 832000 flags = 1 data = length 4000, hash 817CF735 - sample 10: + sample 9: time = 864000 flags = 1 data = length 4000, hash 37391ADA - sample 11: + sample 10: time = 896000 flags = 1 data = length 4000, hash 37391ADA - sample 12: + sample 11: time = 928000 flags = 1 data = length 4000, hash 64DBF751 - sample 13: + sample 12: time = 960000 flags = 1 data = length 4000, hash 81AE828E - sample 14: + sample 13: time = 992000 flags = 1 data = length 4000, hash 767D6C98 - sample 15: + sample 14: time = 1024000 flags = 1 data = length 4000, hash A5F6D4E - sample 16: + sample 15: time = 1056000 flags = 1 data = length 4000, hash EABC6B0D - sample 17: + sample 16: time = 1088000 flags = 1 data = length 4000, hash F47EF742 - sample 18: + sample 17: time = 1120000 flags = 1 data = length 4000, hash 9B2549DA - sample 19: + sample 18: time = 1152000 flags = 1 data = length 4000, hash A12733C9 - sample 20: + sample 19: time = 1184000 flags = 1 data = length 4000, hash 95F62E99 - sample 21: + sample 20: time = 1216000 flags = 1 data = length 4000, hash A4D858 - sample 22: + sample 21: time = 1248000 flags = 1 data = length 4000, hash A4D858 - sample 23: + sample 22: time = 1280000 flags = 1 data = length 4000, hash 22C1A129 - sample 24: + sample 23: time = 1312000 flags = 1 data = length 4000, hash 2C51E4A1 - sample 25: + sample 24: time = 1344000 flags = 1 data = length 4000, hash 3782E8BB - sample 26: + sample 25: time = 1376000 flags = 1 data = length 4000, hash 2C51E4A1 - sample 27: + sample 26: time = 1408000 flags = 1 data = length 4000, hash BDB3D129 - sample 28: + sample 27: time = 1440000 flags = 1 data = length 4000, hash F642A55 - sample 29: + sample 28: time = 1472000 flags = 1 data = length 4000, hash 32F259F4 - sample 30: + sample 29: time = 1504000 flags = 1 data = length 4000, hash 4C987B7C - sample 31: + sample 30: time = 1536000 flags = 1 data = length 4000, hash 57C98E1C - sample 32: + sample 31: time = 1568000 flags = 1 data = length 4000, hash 4C987B7C - sample 33: + sample 32: time = 1600000 flags = 1 data = length 4000, hash 4C987B7C - sample 34: + sample 33: time = 1632000 flags = 1 data = length 4000, hash 4C987B7C - sample 35: + sample 34: time = 1664000 flags = 1 data = length 4000, hash 4C987B7C - sample 36: + sample 35: time = 1696000 flags = 1 data = length 4000, hash 4C987B7C diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index db94e2636e..a079fe334e 100644 --- a/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/testdata/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -7,8 +7,8 @@ seekMap: getPosition(1728000) = [[timeUs=0, position=638]] numberOfTracks = 1 track 0: - total output bytes = 76000 - sample count = 19 + total output bytes = 72000 + sample count = 18 format 0: peakBitrate = 1000000 id = 1 @@ -17,78 +17,74 @@ track 0: sampleRate = 48000 language = und sample 0: - time = 1120000 - flags = 1 - data = length 4000, hash 9B2549DA - sample 1: time = 1152000 flags = 1 data = length 4000, hash A12733C9 - sample 2: + sample 1: time = 1184000 flags = 1 data = length 4000, hash 95F62E99 - sample 3: + sample 2: time = 1216000 flags = 1 data = length 4000, hash A4D858 - sample 4: + sample 3: time = 1248000 flags = 1 data = length 4000, hash A4D858 - sample 5: + sample 4: time = 1280000 flags = 1 data = length 4000, hash 22C1A129 - sample 6: + sample 5: time = 1312000 flags = 1 data = length 4000, hash 2C51E4A1 - sample 7: + sample 6: time = 1344000 flags = 1 data = length 4000, hash 3782E8BB - sample 8: + sample 7: time = 1376000 flags = 1 data = length 4000, hash 2C51E4A1 - sample 9: + sample 8: time = 1408000 flags = 1 data = length 4000, hash BDB3D129 - sample 10: + sample 9: time = 1440000 flags = 1 data = length 4000, hash F642A55 - sample 11: + sample 10: time = 1472000 flags = 1 data = length 4000, hash 32F259F4 - sample 12: + sample 11: time = 1504000 flags = 1 data = length 4000, hash 4C987B7C - sample 13: + sample 12: time = 1536000 flags = 1 data = length 4000, hash 57C98E1C - sample 14: + sample 13: time = 1568000 flags = 1 data = length 4000, hash 4C987B7C - sample 15: + sample 14: time = 1600000 flags = 1 data = length 4000, hash 4C987B7C - sample 16: + sample 15: time = 1632000 flags = 1 data = length 4000, hash 4C987B7C - sample 17: + sample 16: time = 1664000 flags = 1 data = length 4000, hash 4C987B7C - sample 18: + sample 17: time = 1696000 flags = 1 data = length 4000, hash 4C987B7C From 4dfa7ca249f8a32fb76a1fbde2327ed3e2e7c9f4 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 31 Jan 2023 16:55:07 +0000 Subject: [PATCH 097/104] Fix (another) `LeanbackPlayerAdapter` param name mismatch I missed this when fixing `positionInMs` for Dackka in https://github.com/google/ExoPlayer/commit/d2a3d8f6fabb6d22ac28a76379725d0915344cba This time I manually verified that all the `@Override` methods have parameter names that match [the docs](https://developer.android.com/reference/androidx/leanback/media/PlayerAdapter). #minor-release PiperOrigin-RevId: 506017063 (cherry picked from commit 736f090cce540de78a968e62fd8c5aec413e8122) --- .../exoplayer2/ext/leanback/LeanbackPlayerAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java index d245aaa07f..e7c517b271 100644 --- a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java +++ b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java @@ -110,9 +110,9 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab } @Override - public void setProgressUpdatingEnabled(boolean enabled) { + public void setProgressUpdatingEnabled(boolean enable) { handler.removeCallbacks(this); - if (enabled) { + if (enable) { handler.post(this); } } From 76e87406bbeab064ca14f179771636a70f46308e Mon Sep 17 00:00:00 2001 From: christosts Date: Wed, 1 Feb 2023 15:32:27 +0000 Subject: [PATCH 098/104] Merge pull request #10793 from fraunhoferfokus:dash-thumbnail-support PiperOrigin-RevId: 506261584 (cherry picked from commit 107e0c6e42c6fd080b1be19e5305597cf880a9dd) --- .../com/google/android/exoplayer2/Format.java | 70 +++++++++++++++++++ .../google/android/exoplayer2/FormatTest.java | 2 + .../source/dash/DashMediaSource.java | 16 +++-- .../dash/manifest/DashManifestParser.java | 44 +++++++++++- .../dash/manifest/DashManifestParserTest.java | 16 +++-- .../test/assets/media/mpd/sample_mpd_images | 5 +- 6 files changed, 140 insertions(+), 13 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index 5ad355ce2d..4e15771f9a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -108,6 +108,13 @@ import java.util.UUID; *

        *
      • {@link #accessibilityChannel} *
      + * + *

      Fields relevant to image formats

      + * + *
        + *
      • {@link #tileCountHorizontal} + *
      • {@link #tileCountVertical} + *
      */ public final class Format implements Bundleable { @@ -167,6 +174,11 @@ public final class Format implements Bundleable { private int accessibilityChannel; + // Image specific + + private int tileCountHorizontal; + private int tileCountVertical; + // Provided by the source. private @C.CryptoType int cryptoType; @@ -190,6 +202,9 @@ public final class Format implements Bundleable { pcmEncoding = NO_VALUE; // Text specific. accessibilityChannel = NO_VALUE; + // Image specific. + tileCountHorizontal = NO_VALUE; + tileCountVertical = NO_VALUE; // Provided by the source. cryptoType = C.CRYPTO_TYPE_NONE; } @@ -234,6 +249,9 @@ public final class Format implements Bundleable { this.encoderPadding = format.encoderPadding; // Text specific. this.accessibilityChannel = format.accessibilityChannel; + // Image specific. + this.tileCountHorizontal = format.tileCountHorizontal; + this.tileCountVertical = format.tileCountVertical; // Provided by the source. this.cryptoType = format.cryptoType; } @@ -609,6 +627,32 @@ public final class Format implements Bundleable { return this; } + // Image specific. + + /** + * Sets {@link Format#tileCountHorizontal}. The default value is {@link #NO_VALUE}. + * + * @param tileCountHorizontal The {@link Format#accessibilityChannel}. + * @return The builder. + */ + @CanIgnoreReturnValue + public Builder setTileCountHorizontal(int tileCountHorizontal) { + this.tileCountHorizontal = tileCountHorizontal; + return this; + } + + /** + * Sets {@link Format#tileCountVertical}. The default value is {@link #NO_VALUE}. + * + * @param tileCountVertical The {@link Format#accessibilityChannel}. + * @return The builder. + */ + @CanIgnoreReturnValue + public Builder setTileCountVertical(int tileCountVertical) { + this.tileCountVertical = tileCountVertical; + return this; + } + // Provided by source. /** @@ -781,6 +825,15 @@ public final class Format implements Bundleable { /** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */ public final int accessibilityChannel; + // Image specific. + + /** + * The number of horizontal tiles in an image, or {@link #NO_VALUE} if not known or applicable. + */ + public final int tileCountHorizontal; + /** The number of vertical tiles in an image, or {@link #NO_VALUE} if not known or applicable. */ + public final int tileCountVertical; + // Provided by source. /** @@ -1004,6 +1057,9 @@ public final class Format implements Bundleable { encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding; // Text specific. accessibilityChannel = builder.accessibilityChannel; + // Image specific. + tileCountHorizontal = builder.tileCountHorizontal; + tileCountVertical = builder.tileCountVertical; // Provided by source. if (builder.cryptoType == C.CRYPTO_TYPE_NONE && drmInitData != null) { // Encrypted content cannot use CRYPTO_TYPE_NONE. @@ -1250,6 +1306,9 @@ public final class Format implements Bundleable { result = 31 * result + encoderPadding; // Text specific. result = 31 * result + accessibilityChannel; + // Image specific. + result = 31 * result + tileCountHorizontal; + result = 31 * result + tileCountVertical; // Provided by the source. result = 31 * result + cryptoType; hashCode = result; @@ -1286,6 +1345,8 @@ public final class Format implements Bundleable { && encoderDelay == other.encoderDelay && encoderPadding == other.encoderPadding && accessibilityChannel == other.accessibilityChannel + && tileCountHorizontal == other.tileCountHorizontal + && tileCountVertical == other.tileCountVertical && cryptoType == other.cryptoType && Float.compare(frameRate, other.frameRate) == 0 && Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0 @@ -1480,6 +1541,8 @@ public final class Format implements Bundleable { private static final String FIELD_ENCODER_PADDING = Util.intToStringMaxRadix(27); private static final String FIELD_ACCESSIBILITY_CHANNEL = Util.intToStringMaxRadix(28); private static final String FIELD_CRYPTO_TYPE = Util.intToStringMaxRadix(29); + private static final String FIELD_TILE_COUNT_HORIZONTAL = Util.intToStringMaxRadix(30); + private static final String FIELD_TILE_COUNT_VERTICAL = Util.intToStringMaxRadix(31); @Override public Bundle toBundle() { @@ -1535,6 +1598,9 @@ public final class Format implements Bundleable { bundle.putInt(FIELD_ENCODER_PADDING, encoderPadding); // Text specific. bundle.putInt(FIELD_ACCESSIBILITY_CHANNEL, accessibilityChannel); + // Image specific. + bundle.putInt(FIELD_TILE_COUNT_HORIZONTAL, tileCountHorizontal); + bundle.putInt(FIELD_TILE_COUNT_VERTICAL, tileCountVertical); // Source specific. bundle.putInt(FIELD_CRYPTO_TYPE, cryptoType); return bundle; @@ -1599,6 +1665,10 @@ public final class Format implements Bundleable { // Text specific. .setAccessibilityChannel( bundle.getInt(FIELD_ACCESSIBILITY_CHANNEL, DEFAULT.accessibilityChannel)) + // Image specific. + .setTileCountHorizontal( + bundle.getInt(FIELD_TILE_COUNT_HORIZONTAL, DEFAULT.tileCountHorizontal)) + .setTileCountVertical(bundle.getInt(FIELD_TILE_COUNT_VERTICAL, DEFAULT.tileCountVertical)) // Source specific. .setCryptoType(bundle.getInt(FIELD_CRYPTO_TYPE, DEFAULT.cryptoType)); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java index 9218658568..aff55a1b06 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java @@ -115,6 +115,8 @@ public final class FormatTest { .setEncoderPadding(1002) .setAccessibilityChannel(2) .setCryptoType(C.CRYPTO_TYPE_CUSTOM_BASE) + .setTileCountHorizontal(20) + .setTileCountVertical(40) .build(); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 853f76b4a4..e875f6c928 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -1056,9 +1056,11 @@ public final class DashMediaSource extends BaseMediaSource { for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); List representations = adaptationSet.representations; - // Exclude text adaptation sets from duration calculations, if we have at least one audio - // or video adaptation set. See: https://github.com/google/ExoPlayer/issues/4029 - if ((haveAudioVideoAdaptationSets && adaptationSet.type == C.TRACK_TYPE_TEXT) + // Exclude other adaptation sets from duration calculations, if we have at least one audio or + // video adaptation set. See: https://github.com/google/ExoPlayer/issues/4029. + boolean adaptationSetIsNotAudioVideo = + adaptationSet.type != C.TRACK_TYPE_AUDIO && adaptationSet.type != C.TRACK_TYPE_VIDEO; + if ((haveAudioVideoAdaptationSets && adaptationSetIsNotAudioVideo) || representations.isEmpty()) { continue; } @@ -1088,9 +1090,11 @@ public final class DashMediaSource extends BaseMediaSource { for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); List representations = adaptationSet.representations; - // Exclude text adaptation sets from duration calculations, if we have at least one audio - // or video adaptation set. See: https://github.com/google/ExoPlayer/issues/4029 - if ((haveAudioVideoAdaptationSets && adaptationSet.type == C.TRACK_TYPE_TEXT) + // Exclude other adaptation sets from duration calculations, if we have at least one audio or + // video adaptation set. See: https://github.com/google/ExoPlayer/issues/4029 + boolean adaptationSetIsNotAudioVideo = + adaptationSet.type != C.TRACK_TYPE_AUDIO && adaptationSet.type != C.TRACK_TYPE_VIDEO; + if ((haveAudioVideoAdaptationSets && adaptationSetIsNotAudioVideo) || representations.isEmpty()) { continue; } 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 997e0e2ff7..9235f8c830 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 @@ -555,7 +555,9 @@ public class DashManifestParser extends DefaultHandler ? C.TRACK_TYPE_VIDEO : MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? C.TRACK_TYPE_TEXT - : C.TRACK_TYPE_UNKNOWN; + : MimeTypes.BASE_TYPE_IMAGE.equals(contentType) + ? C.TRACK_TYPE_IMAGE + : C.TRACK_TYPE_UNKNOWN; } /** @@ -808,6 +810,7 @@ public class DashManifestParser extends DefaultHandler roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors); roleFlags |= parseRoleFlagsFromProperties(essentialProperties); roleFlags |= parseRoleFlagsFromProperties(supplementalProperties); + @Nullable Pair tileCounts = parseTileCountFromProperties(essentialProperties); Format.Builder formatBuilder = new Format.Builder() @@ -818,7 +821,9 @@ public class DashManifestParser extends DefaultHandler .setPeakBitrate(bitrate) .setSelectionFlags(selectionFlags) .setRoleFlags(roleFlags) - .setLanguage(language); + .setLanguage(language) + .setTileCountHorizontal(tileCounts != null ? tileCounts.first : Format.NO_VALUE) + .setTileCountVertical(tileCounts != null ? tileCounts.second : Format.NO_VALUE); if (MimeTypes.isVideo(sampleMimeType)) { formatBuilder.setWidth(width).setHeight(height).setFrameRate(frameRate); @@ -1627,6 +1632,41 @@ public class DashManifestParser extends DefaultHandler return attributeValue.split(","); } + // Thumbnail tile information parsing + + /** + * Parses given descriptors for thumbnail tile information. + * + * @param essentialProperties List of descriptors that contain thumbnail tile information. + * @return A pair of Integer values, where the first is the count of horizontal tiles and the + * second is the count of vertical tiles, or null if no thumbnail tile information is found. + */ + @Nullable + protected Pair parseTileCountFromProperties( + List essentialProperties) { + for (int i = 0; i < essentialProperties.size(); i++) { + Descriptor descriptor = essentialProperties.get(i); + if ((Ascii.equalsIgnoreCase("http://dashif.org/thumbnail_tile", descriptor.schemeIdUri) + || Ascii.equalsIgnoreCase( + "http://dashif.org/guidelines/thumbnail_tile", descriptor.schemeIdUri)) + && descriptor.value != null) { + String size = descriptor.value; + String[] sizeSplit = Util.split(size, "x"); + if (sizeSplit.length != 2) { + continue; + } + try { + int tileCountHorizontal = Integer.parseInt(sizeSplit[0]); + int tileCountVertical = Integer.parseInt(sizeSplit[1]); + return Pair.create(tileCountHorizontal, tileCountVertical); + } catch (NumberFormatException e) { + // Ignore property if it's malformed. + } + } + } + return null; + } + // Utility methods. /** 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 854ae54d63..ac7ae3e4ad 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 @@ -252,11 +252,19 @@ public class DashManifestParserTest { ApplicationProvider.getApplicationContext(), SAMPLE_MPD_IMAGES)); AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(0); - Format format = adaptationSet.representations.get(0).format; + Format format0 = adaptationSet.representations.get(0).format; + Format format1 = adaptationSet.representations.get(1).format; - assertThat(format.sampleMimeType).isEqualTo("image/jpeg"); - assertThat(format.width).isEqualTo(320); - assertThat(format.height).isEqualTo(180); + assertThat(format0.sampleMimeType).isEqualTo("image/jpeg"); + assertThat(format0.width).isEqualTo(320); + assertThat(format0.height).isEqualTo(180); + assertThat(format0.tileCountHorizontal).isEqualTo(12); + assertThat(format0.tileCountVertical).isEqualTo(16); + assertThat(format1.sampleMimeType).isEqualTo("image/jpeg"); + assertThat(format1.width).isEqualTo(640); + assertThat(format1.height).isEqualTo(360); + assertThat(format1.tileCountHorizontal).isEqualTo(2); + assertThat(format1.tileCountVertical).isEqualTo(4); } @Test diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_images b/testdata/src/test/assets/media/mpd/sample_mpd_images index 981a29a23a..7d0779e957 100644 --- a/testdata/src/test/assets/media/mpd/sample_mpd_images +++ b/testdata/src/test/assets/media/mpd/sample_mpd_images @@ -4,7 +4,10 @@ - + + + + From 7269d2a4c2585139892582e1adc32618f59da46c Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 1 Feb 2023 13:06:28 +0000 Subject: [PATCH 099/104] Publish ConcatenatingMediaSource2 Can be used to combine multiple media items into a single timeline window. Issue: androidx/media#247 Issue: google/ExoPlayer#4868 PiperOrigin-RevId: 506283307 (cherry picked from commit eb8fffba15810b8206653d8ffaff233d823fbfc9) --- .../source/ConcatenatingMediaSource2.java | 608 ++++++++++++ .../source/ConcatenatingMediaSource2Test.java | 911 ++++++++++++++++++ 2 files changed, 1519 insertions(+) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2Test.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2.java new file mode 100644 index 0000000000..10af495e01 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2.java @@ -0,0 +1,608 @@ +/* + * Copyright 2021 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; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.util.Pair; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.IdentityHashMap; + +/** + * Concatenates multiple {@link MediaSource MediaSources}, combining everything in one single {@link + * Timeline.Window}. + * + *

      This class can only be used under the following conditions: + * + *

        + *
      • All sources must be non-empty. + *
      • All {@link Timeline.Window Windows} defined by the sources, except the first, must have an + * {@link Timeline.Window#getPositionInFirstPeriodUs() period offset} of zero. This excludes, + * for example, live streams or {@link ClippingMediaSource} with a non-zero start position. + *
      + */ +public final class ConcatenatingMediaSource2 extends CompositeMediaSource { + + /** A builder for {@link ConcatenatingMediaSource2} instances. */ + public static final class Builder { + + private final ImmutableList.Builder mediaSourceHoldersBuilder; + + private int index; + @Nullable private MediaItem mediaItem; + @Nullable private MediaSource.Factory mediaSourceFactory; + + /** Creates the builder. */ + public Builder() { + mediaSourceHoldersBuilder = ImmutableList.builder(); + } + + /** + * Instructs the builder to use a {@link DefaultMediaSourceFactory} to convert {@link MediaItem + * MediaItems} to {@link MediaSource MediaSources} for all future calls to {@link + * #add(MediaItem)} or {@link #add(MediaItem, long)}. + * + * @param context A {@link Context}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder useDefaultMediaSourceFactory(Context context) { + return setMediaSourceFactory(new DefaultMediaSourceFactory(context)); + } + + /** + * Sets a {@link MediaSource.Factory} that is used to convert {@link MediaItem MediaItems} to + * {@link MediaSource MediaSources} for all future calls to {@link #add(MediaItem)} or {@link + * #add(MediaItem, long)}. + * + * @param mediaSourceFactory A {@link MediaSource.Factory}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setMediaSourceFactory(MediaSource.Factory mediaSourceFactory) { + this.mediaSourceFactory = checkNotNull(mediaSourceFactory); + return this; + } + + /** + * Sets the {@link MediaItem} to be used for the concatenated media source. + * + *

      This {@link MediaItem} will be used as {@link Timeline.Window#mediaItem} for the + * concatenated source and will be returned by {@link Player#getCurrentMediaItem()}. + * + *

      The default is {@code MediaItem.fromUri(Uri.EMPTY)}. + * + * @param mediaItem The {@link MediaItem}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setMediaItem(MediaItem mediaItem) { + this.mediaItem = mediaItem; + return this; + } + + /** + * Adds a {@link MediaItem} to the concatenation. + * + *

      {@link #useDefaultMediaSourceFactory(Context)} or {@link + * #setMediaSourceFactory(MediaSource.Factory)} must be called before this method. + * + *

      This method must not be used with media items for progressive media that can't provide + * their duration with their first {@link Timeline} update. Use {@link #add(MediaItem, long)} + * instead. + * + * @param mediaItem The {@link MediaItem}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder add(MediaItem mediaItem) { + return add(mediaItem, /* initialPlaceholderDurationMs= */ C.TIME_UNSET); + } + + /** + * Adds a {@link MediaItem} to the concatenation and specifies its initial placeholder duration + * used while the actual duration is still unknown. + * + *

      {@link #useDefaultMediaSourceFactory(Context)} or {@link + * #setMediaSourceFactory(MediaSource.Factory)} must be called before this method. + * + *

      Setting a placeholder duration is required for media items for progressive media that + * can't provide their duration with their first {@link Timeline} update. It may also be used + * for other items to make the duration known immediately. + * + * @param mediaItem The {@link MediaItem}. + * @param initialPlaceholderDurationMs The initial placeholder duration in milliseconds used + * while the actual duration is still unknown, or {@link C#TIME_UNSET} to not define one. + * The placeholder duration is used for every {@link Timeline.Window} defined by {@link + * Timeline} of the {@link MediaItem}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder add(MediaItem mediaItem, long initialPlaceholderDurationMs) { + checkNotNull(mediaItem); + checkStateNotNull( + mediaSourceFactory, + "Must use useDefaultMediaSourceFactory or setMediaSourceFactory first."); + return add(mediaSourceFactory.createMediaSource(mediaItem), initialPlaceholderDurationMs); + } + + /** + * Adds a {@link MediaSource} to the concatenation. + * + *

      This method must not be used for sources like {@link ProgressiveMediaSource} that can't + * provide their duration with their first {@link Timeline} update. Use {@link #add(MediaSource, + * long)} instead. + * + * @param mediaSource The {@link MediaSource}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder add(MediaSource mediaSource) { + return add(mediaSource, /* initialPlaceholderDurationMs= */ C.TIME_UNSET); + } + + /** + * Adds a {@link MediaSource} to the concatenation and specifies its initial placeholder + * duration used while the actual duration is still unknown. + * + *

      Setting a placeholder duration is required for sources like {@link ProgressiveMediaSource} + * that can't provide their duration with their first {@link Timeline} update. It may also be + * used for other sources to make the duration known immediately. + * + * @param mediaSource The {@link MediaSource}. + * @param initialPlaceholderDurationMs The initial placeholder duration in milliseconds used + * while the actual duration is still unknown, or {@link C#TIME_UNSET} to not define one. + * The placeholder duration is used for every {@link Timeline.Window} defined by {@link + * Timeline} of the {@link MediaSource}. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder add(MediaSource mediaSource, long initialPlaceholderDurationMs) { + checkNotNull(mediaSource); + checkState( + !(mediaSource instanceof ProgressiveMediaSource) + || initialPlaceholderDurationMs != C.TIME_UNSET, + "Progressive media source must define an initial placeholder duration."); + mediaSourceHoldersBuilder.add( + new MediaSourceHolder(mediaSource, index++, Util.msToUs(initialPlaceholderDurationMs))); + return this; + } + + /** Builds the concatenating media source. */ + public ConcatenatingMediaSource2 build() { + checkArgument(index > 0, "Must add at least one source to the concatenation."); + if (mediaItem == null) { + mediaItem = MediaItem.fromUri(Uri.EMPTY); + } + return new ConcatenatingMediaSource2(mediaItem, mediaSourceHoldersBuilder.build()); + } + } + + private static final int MSG_UPDATE_TIMELINE = 0; + + private final MediaItem mediaItem; + private final ImmutableList mediaSourceHolders; + private final IdentityHashMap mediaSourceByMediaPeriod; + + @Nullable private Handler playbackThreadHandler; + private boolean timelineUpdateScheduled; + + private ConcatenatingMediaSource2( + MediaItem mediaItem, ImmutableList mediaSourceHolders) { + this.mediaItem = mediaItem; + this.mediaSourceHolders = mediaSourceHolders; + mediaSourceByMediaPeriod = new IdentityHashMap<>(); + } + + @Nullable + @Override + public Timeline getInitialTimeline() { + return maybeCreateConcatenatedTimeline(); + } + + @Override + public MediaItem getMediaItem() { + return mediaItem; + } + + @Override + protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { + super.prepareSourceInternal(mediaTransferListener); + playbackThreadHandler = new Handler(/* callback= */ this::handleMessage); + for (int i = 0; i < mediaSourceHolders.size(); i++) { + MediaSourceHolder holder = mediaSourceHolders.get(i); + prepareChildSource(/* id= */ i, holder.mediaSource); + } + scheduleTimelineUpdate(); + } + + @SuppressWarnings("MissingSuperCall") + @Override + protected void enableInternal() { + // Suppress enabling all child sources here as they can be lazily enabled when creating periods. + } + + @Override + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + int holderIndex = getChildIndex(id.periodUid); + MediaSourceHolder holder = mediaSourceHolders.get(holderIndex); + MediaPeriodId childMediaPeriodId = + id.copyWithPeriodUid(getChildPeriodUid(id.periodUid)) + .copyWithWindowSequenceNumber( + getChildWindowSequenceNumber( + id.windowSequenceNumber, mediaSourceHolders.size(), holder.index)); + enableChildSource(holder.index); + holder.activeMediaPeriods++; + MediaPeriod mediaPeriod = + holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); + mediaSourceByMediaPeriod.put(mediaPeriod, holder); + disableUnusedMediaSources(); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); + holder.mediaSource.releasePeriod(mediaPeriod); + holder.activeMediaPeriods--; + if (!mediaSourceByMediaPeriod.isEmpty()) { + disableUnusedMediaSources(); + } + } + + @Override + protected void releaseSourceInternal() { + super.releaseSourceInternal(); + if (playbackThreadHandler != null) { + playbackThreadHandler.removeCallbacksAndMessages(null); + playbackThreadHandler = null; + } + timelineUpdateScheduled = false; + } + + @Override + protected void onChildSourceInfoRefreshed( + Integer childSourceId, MediaSource mediaSource, Timeline newTimeline) { + scheduleTimelineUpdate(); + } + + @Override + @Nullable + protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId( + Integer childSourceId, MediaPeriodId mediaPeriodId) { + int childIndex = + getChildIndexFromChildWindowSequenceNumber( + mediaPeriodId.windowSequenceNumber, mediaSourceHolders.size()); + if (childSourceId != childIndex) { + // Ensure the reported media period id has the expected window sequence number. Otherwise it + // does not belong to this child source. + return null; + } + long windowSequenceNumber = + getWindowSequenceNumberFromChildWindowSequenceNumber( + mediaPeriodId.windowSequenceNumber, mediaSourceHolders.size()); + Object periodUid = getPeriodUid(childSourceId, mediaPeriodId.periodUid); + return mediaPeriodId + .copyWithPeriodUid(periodUid) + .copyWithWindowSequenceNumber(windowSequenceNumber); + } + + @Override + protected int getWindowIndexForChildWindowIndex(Integer childSourceId, int windowIndex) { + return 0; + } + + private boolean handleMessage(Message msg) { + if (msg.what == MSG_UPDATE_TIMELINE) { + updateTimeline(); + } + return true; + } + + private void scheduleTimelineUpdate() { + if (!timelineUpdateScheduled) { + checkNotNull(playbackThreadHandler).obtainMessage(MSG_UPDATE_TIMELINE).sendToTarget(); + timelineUpdateScheduled = true; + } + } + + private void updateTimeline() { + timelineUpdateScheduled = false; + @Nullable ConcatenatedTimeline timeline = maybeCreateConcatenatedTimeline(); + if (timeline != null) { + refreshSourceInfo(timeline); + } + } + + private void disableUnusedMediaSources() { + for (int i = 0; i < mediaSourceHolders.size(); i++) { + MediaSourceHolder holder = mediaSourceHolders.get(i); + if (holder.activeMediaPeriods == 0) { + disableChildSource(holder.index); + } + } + } + + @Nullable + private ConcatenatedTimeline maybeCreateConcatenatedTimeline() { + Timeline.Window window = new Timeline.Window(); + Timeline.Period period = new Timeline.Period(); + ImmutableList.Builder timelinesBuilder = ImmutableList.builder(); + ImmutableList.Builder firstPeriodIndicesBuilder = ImmutableList.builder(); + ImmutableList.Builder periodOffsetsInWindowUsBuilder = ImmutableList.builder(); + int periodCount = 0; + boolean isSeekable = true; + boolean isDynamic = false; + long durationUs = 0; + long defaultPositionUs = 0; + long nextPeriodOffsetInWindowUs = 0; + boolean manifestsAreIdentical = true; + boolean hasInitialManifest = false; + @Nullable Object initialManifest = null; + for (int i = 0; i < mediaSourceHolders.size(); i++) { + MediaSourceHolder holder = mediaSourceHolders.get(i); + Timeline timeline = holder.mediaSource.getTimeline(); + checkArgument(!timeline.isEmpty(), "Can't concatenate empty child Timeline."); + timelinesBuilder.add(timeline); + firstPeriodIndicesBuilder.add(periodCount); + periodCount += timeline.getPeriodCount(); + for (int j = 0; j < timeline.getWindowCount(); j++) { + timeline.getWindow(/* windowIndex= */ j, window); + if (!hasInitialManifest) { + initialManifest = window.manifest; + hasInitialManifest = true; + } + manifestsAreIdentical = + manifestsAreIdentical && Util.areEqual(initialManifest, window.manifest); + + long windowDurationUs = window.durationUs; + if (windowDurationUs == C.TIME_UNSET) { + if (holder.initialPlaceholderDurationUs == C.TIME_UNSET) { + // Source duration isn't known yet and we have no placeholder duration. + return null; + } + windowDurationUs = holder.initialPlaceholderDurationUs; + } + durationUs += windowDurationUs; + if (holder.index == 0 && j == 0) { + defaultPositionUs = window.defaultPositionUs; + nextPeriodOffsetInWindowUs = -window.positionInFirstPeriodUs; + } else { + checkArgument( + window.positionInFirstPeriodUs == 0, + "Can't concatenate windows. A window has a non-zero offset in a period."); + } + // Assume placeholder windows are seekable to not prevent seeking in other periods. + isSeekable &= window.isSeekable || window.isPlaceholder; + isDynamic |= window.isDynamic; + } + int childPeriodCount = timeline.getPeriodCount(); + for (int j = 0; j < childPeriodCount; j++) { + periodOffsetsInWindowUsBuilder.add(nextPeriodOffsetInWindowUs); + timeline.getPeriod(/* periodIndex= */ j, period); + long periodDurationUs = period.durationUs; + if (periodDurationUs == C.TIME_UNSET) { + checkArgument( + childPeriodCount == 1, + "Can't concatenate multiple periods with unknown duration in one window."); + long windowDurationUs = + window.durationUs != C.TIME_UNSET + ? window.durationUs + : holder.initialPlaceholderDurationUs; + periodDurationUs = windowDurationUs + window.positionInFirstPeriodUs; + } + nextPeriodOffsetInWindowUs += periodDurationUs; + } + } + return new ConcatenatedTimeline( + mediaItem, + timelinesBuilder.build(), + firstPeriodIndicesBuilder.build(), + periodOffsetsInWindowUsBuilder.build(), + isSeekable, + isDynamic, + durationUs, + defaultPositionUs, + manifestsAreIdentical ? initialManifest : null); + } + + /** + * Returns the period uid for the concatenated source from the child index and child period uid. + */ + private static Object getPeriodUid(int childIndex, Object childPeriodUid) { + return Pair.create(childIndex, childPeriodUid); + } + + /** Returns the child index from the period uid of the concatenated source. */ + @SuppressWarnings("unchecked") + private static int getChildIndex(Object periodUid) { + return ((Pair) periodUid).first; + } + + /** Returns the uid of child period from the period uid of the concatenated source. */ + @SuppressWarnings("unchecked") + private static Object getChildPeriodUid(Object periodUid) { + return ((Pair) periodUid).second; + } + + /** Returns the window sequence number used for the child source. */ + private static long getChildWindowSequenceNumber( + long windowSequenceNumber, int childCount, int childIndex) { + return windowSequenceNumber * childCount + childIndex; + } + + /** Returns the index of the child source from a child window sequence number. */ + private static int getChildIndexFromChildWindowSequenceNumber( + long childWindowSequenceNumber, int childCount) { + return (int) (childWindowSequenceNumber % childCount); + } + + /** Returns the concatenated window sequence number from a child window sequence number. */ + private static long getWindowSequenceNumberFromChildWindowSequenceNumber( + long childWindowSequenceNumber, int childCount) { + return childWindowSequenceNumber / childCount; + } + + /* package */ static final class MediaSourceHolder { + + public final MaskingMediaSource mediaSource; + public final int index; + public final long initialPlaceholderDurationUs; + + public int activeMediaPeriods; + + public MediaSourceHolder( + MediaSource mediaSource, int index, long initialPlaceholderDurationUs) { + this.mediaSource = new MaskingMediaSource(mediaSource, /* useLazyPreparation= */ false); + this.index = index; + this.initialPlaceholderDurationUs = initialPlaceholderDurationUs; + } + } + + private static final class ConcatenatedTimeline extends Timeline { + + private final MediaItem mediaItem; + private final ImmutableList timelines; + private final ImmutableList firstPeriodIndices; + private final ImmutableList periodOffsetsInWindowUs; + private final boolean isSeekable; + private final boolean isDynamic; + private final long durationUs; + private final long defaultPositionUs; + @Nullable private final Object manifest; + + public ConcatenatedTimeline( + MediaItem mediaItem, + ImmutableList timelines, + ImmutableList firstPeriodIndices, + ImmutableList periodOffsetsInWindowUs, + boolean isSeekable, + boolean isDynamic, + long durationUs, + long defaultPositionUs, + @Nullable Object manifest) { + this.mediaItem = mediaItem; + this.timelines = timelines; + this.firstPeriodIndices = firstPeriodIndices; + this.periodOffsetsInWindowUs = periodOffsetsInWindowUs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + this.durationUs = durationUs; + this.defaultPositionUs = defaultPositionUs; + this.manifest = manifest; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public int getPeriodCount() { + return periodOffsetsInWindowUs.size(); + } + + @Override + public final Window getWindow( + int windowIndex, Window window, long defaultPositionProjectionUs) { + return window.set( + Window.SINGLE_WINDOW_UID, + mediaItem, + manifest, + /* presentationStartTimeMs= */ C.TIME_UNSET, + /* windowStartTimeMs= */ C.TIME_UNSET, + /* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET, + isSeekable, + isDynamic, + /* liveConfiguration= */ null, + defaultPositionUs, + durationUs, + /* firstPeriodIndex= */ 0, + /* lastPeriodIndex= */ getPeriodCount() - 1, + /* positionInFirstPeriodUs= */ -periodOffsetsInWindowUs.get(0)); + } + + @Override + public final Period getPeriodByUid(Object periodUid, Period period) { + int childIndex = getChildIndex(periodUid); + Object childPeriodUid = getChildPeriodUid(periodUid); + Timeline timeline = timelines.get(childIndex); + int periodIndex = + firstPeriodIndices.get(childIndex) + timeline.getIndexOfPeriod(childPeriodUid); + timeline.getPeriodByUid(childPeriodUid, period); + period.windowIndex = 0; + period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex); + period.uid = periodUid; + return period; + } + + @Override + public final Period getPeriod(int periodIndex, Period period, boolean setIds) { + int childIndex = getChildIndexByPeriodIndex(periodIndex); + int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex); + timelines.get(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds); + period.windowIndex = 0; + period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex); + if (setIds) { + period.uid = getPeriodUid(childIndex, checkNotNull(period.uid)); + } + return period; + } + + @Override + public final int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair) || !(((Pair) uid).first instanceof Integer)) { + return C.INDEX_UNSET; + } + int childIndex = getChildIndex(uid); + Object periodUid = getChildPeriodUid(uid); + int periodIndexInChild = timelines.get(childIndex).getIndexOfPeriod(periodUid); + return periodIndexInChild == C.INDEX_UNSET + ? C.INDEX_UNSET + : firstPeriodIndices.get(childIndex) + periodIndexInChild; + } + + @Override + public final Object getUidOfPeriod(int periodIndex) { + int childIndex = getChildIndexByPeriodIndex(periodIndex); + int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex); + Object periodUidInChild = + timelines.get(childIndex).getUidOfPeriod(periodIndex - firstPeriodIndexInChild); + return getPeriodUid(childIndex, periodUidInChild); + } + + private int getChildIndexByPeriodIndex(int periodIndex) { + return Util.binarySearchFloor( + firstPeriodIndices, periodIndex + 1, /* inclusive= */ false, /* stayInBounds= */ false); + } + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2Test.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2Test.java new file mode 100644 index 0000000000..746ae1a370 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource2Test.java @@ -0,0 +1,911 @@ +/* + * Copyright 2021 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; + +import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; +import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.Looper; +import android.util.Pair; +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.analytics.PlayerId; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; +import com.google.android.exoplayer2.testutil.FakeMediaSource; +import com.google.android.exoplayer2.testutil.FakeTimeline; +import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.EventLogger; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; + +/** Unit tests for {@link ConcatenatingMediaSource2}. */ +@RunWith(ParameterizedRobolectricTestRunner.class) +public final class ConcatenatingMediaSource2Test { + + @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") + public static ImmutableList params() { + ImmutableList.Builder builder = ImmutableList.builder(); + + // Full example with an offset in the initial window, MediaSource with multiple windows and + // periods, and sources with ad insertion. + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ 123, /* adGroupTimesUs...= */ 0, 300_000) + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) + .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1) + .withAdDurationsUs(new long[][] {new long[] {2_000_000}, new long[] {4_000_000}}); + builder.add( + new TestConfig( + "initial_offset_multiple_windows_and_ads", + buildConcatenatingMediaSource( + buildMediaSource( + buildWindow( + /* periodCount= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 1000, + /* defaultPositionMs= */ 123, + /* windowOffsetInFirstPeriodMs= */ 50), + buildWindow( + /* periodCount= */ 2, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationMs= */ 2500)), + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 500, + adPlaybackState)), + buildMediaSource( + buildWindow( + /* periodCount= */ 3, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 1800))), + /* expectedAdDiscontinuities= */ 3, + new ExpectedTimelineData( + /* isSeekable= */ false, + /* isDynamic= */ false, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {550, 500, 1250, 1250, 500, 600, 600, 600}, + /* periodOffsetsInWindowMs= */ new long[] { + -50, 500, 1000, 2250, 3500, 4000, 4600, 5200 + }, + /* periodIsPlaceholder= */ new boolean[] { + false, false, false, false, false, false, false, false + }, + /* windowDurationMs= */ 5800, + /* manifest= */ null) + .withAdPlaybackState(/* periodIndex= */ 4, adPlaybackState))); + + builder.add( + new TestConfig( + "multipleMediaSource_sameManifest", + buildConcatenatingMediaSource( + buildMediaSource( + new Object[] {"manifest"}, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ 1000)), + buildMediaSource( + new Object[] {"manifest"}, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ 1000))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {1000, 1000}, + /* periodOffsetsInWindowMs= */ new long[] {0, 1000}, + /* periodIsPlaceholder= */ new boolean[] {false, false}, + /* windowDurationMs= */ 2000, + /* manifest= */ "manifest"))); + + builder.add( + new TestConfig( + "multipleMediaSource_differentManifest", + buildConcatenatingMediaSource( + buildMediaSource( + new Object[] {"manifest1"}, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ 1000)), + buildMediaSource( + new Object[] {"manifest2"}, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ 1000))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {1000, 1000}, + /* periodOffsetsInWindowMs= */ new long[] {0, 1000}, + /* periodIsPlaceholder= */ new boolean[] {false, false}, + /* windowDurationMs= */ 2000, + /* manifest= */ null))); + + // Counter-example for isSeekable and isDynamic. + builder.add( + new TestConfig( + "isSeekable_isDynamic_counter_example", + buildConcatenatingMediaSource( + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 1000)), + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ 500))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {1000, 500}, + /* periodOffsetsInWindowMs= */ new long[] {0, 1000}, + /* periodIsPlaceholder= */ new boolean[] {false, false}, + /* windowDurationMs= */ 1500, + /* manifest= */ null))); + + // Unknown window and period durations. + builder.add( + new TestConfig( + "unknown_window_and_period_durations", + buildConcatenatingMediaSource( + /* placeholderDurationMs= */ 420, + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* durationMs= */ C.TIME_UNSET, + /* defaultPositionMs= */ 123, + /* windowOffsetInFirstPeriodMs= */ 50)), + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ C.TIME_UNSET))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {C.TIME_UNSET, C.TIME_UNSET}, + /* periodOffsetsInWindowMs= */ new long[] {0, 420}, + /* periodIsPlaceholder= */ new boolean[] {true, true}, + /* windowDurationMs= */ 840, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {C.TIME_UNSET, C.TIME_UNSET}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 420}, + /* periodIsPlaceholder= */ new boolean[] {false, false}, + /* windowDurationMs= */ 840, + /* manifest= */ null))); + + // Duplicate sources and nested concatenation. + builder.add( + new TestConfig( + "duplicated_and_nested_sources", + () -> { + MediaSource duplicateSource = + buildMediaSource( + buildWindow( + /* periodCount= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 1000)) + .get(); + Supplier duplicateSourceSupplier = () -> duplicateSource; + return buildConcatenatingMediaSource( + duplicateSourceSupplier, + buildConcatenatingMediaSource( + duplicateSourceSupplier, duplicateSourceSupplier), + buildConcatenatingMediaSource( + duplicateSourceSupplier, duplicateSourceSupplier), + duplicateSourceSupplier) + .get(); + }, + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ false, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] { + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 + }, + /* periodOffsetsInWindowMs= */ new long[] { + 0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500 + }, + /* periodIsPlaceholder= */ new boolean[] { + false, false, false, false, false, false, false, false, false, false, false, false + }, + /* windowDurationMs= */ 6000, + /* manifest= */ null))); + + // Concatenation with initial placeholder durations and delayed timeline updates. + builder.add( + new TestConfig( + "initial_placeholder_and_delayed_preparation", + buildConcatenatingMediaSource( + /* placeholderDurationMs= */ 5000, + buildMediaSource( + /* preparationDelayCount= */ 1, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 4000, + /* defaultPositionMs= */ 123, + /* windowOffsetInFirstPeriodMs= */ 50)), + buildMediaSource( + /* preparationDelayCount= */ 3, + buildWindow( + /* periodCount= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 7000)), + buildMediaSource( + /* preparationDelayCount= */ 2, + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationMs= */ 6000))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}, + /* periodOffsetsInWindowMs= */ new long[] {0, 5000, 10000}, + /* periodIsPlaceholder= */ new boolean[] {true, true, true}, + /* windowDurationMs= */ 15000, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {4050, C.TIME_UNSET, C.TIME_UNSET}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 9000}, + /* periodIsPlaceholder= */ new boolean[] {false, true, true}, + /* windowDurationMs= */ 14000, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ false, + /* isDynamic= */ true, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {4050, C.TIME_UNSET, 6000}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 9000}, + /* periodIsPlaceholder= */ new boolean[] {false, true, false}, + /* windowDurationMs= */ 15000, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ false, + /* isDynamic= */ false, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {4050, 3500, 3500, 6000}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 7500, 11000}, + /* periodIsPlaceholder= */ new boolean[] {false, false, false, false}, + /* windowDurationMs= */ 17000, + /* manifest= */ null))); + + // Concatenation with initial placeholder durations and some immediate timeline updates. + builder.add( + new TestConfig( + "initial_placeholder_and_immediate_partial_preparation", + buildConcatenatingMediaSource( + /* placeholderDurationMs= */ 5000, + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 4000, + /* defaultPositionMs= */ 123, + /* windowOffsetInFirstPeriodMs= */ 50)), + buildMediaSource( + /* preparationDelayCount= */ 1, + buildWindow( + /* periodCount= */ 2, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationMs= */ 7000)), + buildMediaSource( + buildWindow( + /* periodCount= */ 1, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationMs= */ 6000))), + /* expectedAdDiscontinuities= */ 0, + new ExpectedTimelineData( + /* isSeekable= */ true, + /* isDynamic= */ true, + /* defaultPositionMs= */ 0, + /* periodDurationsMs= */ new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET}, + /* periodOffsetsInWindowMs= */ new long[] {0, 5000, 10000}, + /* periodIsPlaceholder= */ new boolean[] {true, true, true}, + /* windowDurationMs= */ 15000, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ false, + /* isDynamic= */ true, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {4050, C.TIME_UNSET, 6000}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 9000}, + /* periodIsPlaceholder= */ new boolean[] {false, true, false}, + /* windowDurationMs= */ 15000, + /* manifest= */ null), + new ExpectedTimelineData( + /* isSeekable= */ false, + /* isDynamic= */ false, + /* defaultPositionMs= */ 123, + /* periodDurationsMs= */ new long[] {4050, 3500, 3500, 6000}, + /* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 7500, 11000}, + /* periodIsPlaceholder= */ new boolean[] {false, false, false, false}, + /* windowDurationMs= */ 17000, + /* manifest= */ null))); + return builder.build(); + } + + @ParameterizedRobolectricTestRunner.Parameter public TestConfig config; + + private static final String TEST_MEDIA_ITEM_ID = "test_media_item_id"; + + @Test + public void prepareSource_reportsExpectedTimelines() throws Exception { + MediaSource mediaSource = config.mediaSourceSupplier.get(); + ArrayList timelines = new ArrayList<>(); + mediaSource.prepareSource( + (source, timeline) -> timelines.add(timeline), + /* mediaTransferListener= */ null, + PlayerId.UNSET); + runMainLooperUntil(() -> timelines.size() == config.expectedTimelineData.size()); + + for (int i = 0; i < config.expectedTimelineData.size(); i++) { + Timeline timeline = timelines.get(i); + ExpectedTimelineData expectedData = config.expectedTimelineData.get(i); + assertThat(timeline.getWindowCount()).isEqualTo(1); + assertThat(timeline.getPeriodCount()).isEqualTo(expectedData.periodDurationsMs.length); + + Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()); + assertThat(window.getDurationMs()).isEqualTo(expectedData.windowDurationMs); + assertThat(window.isDynamic).isEqualTo(expectedData.isDynamic); + assertThat(window.isSeekable).isEqualTo(expectedData.isSeekable); + assertThat(window.getDefaultPositionMs()).isEqualTo(expectedData.defaultPositionMs); + assertThat(window.getPositionInFirstPeriodMs()) + .isEqualTo(-expectedData.periodOffsetsInWindowMs[0]); + assertThat(window.firstPeriodIndex).isEqualTo(0); + assertThat(window.lastPeriodIndex).isEqualTo(expectedData.periodDurationsMs.length - 1); + assertThat(window.uid).isEqualTo(Timeline.Window.SINGLE_WINDOW_UID); + assertThat(window.mediaItem.mediaId).isEqualTo(TEST_MEDIA_ITEM_ID); + assertThat(window.isPlaceholder).isFalse(); + assertThat(window.elapsedRealtimeEpochOffsetMs).isEqualTo(C.TIME_UNSET); + assertThat(window.presentationStartTimeMs).isEqualTo(C.TIME_UNSET); + assertThat(window.windowStartTimeMs).isEqualTo(C.TIME_UNSET); + assertThat(window.liveConfiguration).isNull(); + assertThat(window.manifest).isEqualTo(expectedData.manifest); + + HashSet uidSet = new HashSet<>(); + for (int j = 0; j < timeline.getPeriodCount(); j++) { + Timeline.Period period = + timeline.getPeriod(/* periodIndex= */ j, new Timeline.Period(), /* setIds= */ true); + assertThat(period.getDurationMs()).isEqualTo(expectedData.periodDurationsMs[j]); + assertThat(period.windowIndex).isEqualTo(0); + assertThat(period.getPositionInWindowMs()) + .isEqualTo(expectedData.periodOffsetsInWindowMs[j]); + assertThat(period.isPlaceholder).isEqualTo(expectedData.periodIsPlaceholder[j]); + uidSet.add(period.uid); + assertThat(timeline.getIndexOfPeriod(period.uid)).isEqualTo(j); + assertThat(timeline.getUidOfPeriod(j)).isEqualTo(period.uid); + assertThat(timeline.getPeriodByUid(period.uid, new Timeline.Period())).isEqualTo(period); + } + assertThat(uidSet).hasSize(timeline.getPeriodCount()); + } + } + + @Test + public void prepareSource_afterRelease_reportsSameFinalTimeline() throws Exception { + // Fully prepare source once. + MediaSource mediaSource = config.mediaSourceSupplier.get(); + ArrayList timelines = new ArrayList<>(); + MediaSource.MediaSourceCaller caller = (source, timeline) -> timelines.add(timeline); + mediaSource.prepareSource(caller, /* mediaTransferListener= */ null, PlayerId.UNSET); + runMainLooperUntil(() -> timelines.size() == config.expectedTimelineData.size()); + + // Release and re-prepare. + mediaSource.releaseSource(caller); + AtomicReference secondTimeline = new AtomicReference<>(); + MediaSource.MediaSourceCaller secondCaller = (source, timeline) -> secondTimeline.set(timeline); + mediaSource.prepareSource(secondCaller, /* mediaTransferListener= */ null, PlayerId.UNSET); + + // Assert that we receive the same final timeline. + runMainLooperUntil(() -> Iterables.getLast(timelines).equals(secondTimeline.get())); + } + + @Test + public void preparePeriod_reportsExpectedPeriodLoadEvents() throws Exception { + // Prepare source and register listener. + MediaSource mediaSource = config.mediaSourceSupplier.get(); + MediaSourceEventListener eventListener = mock(MediaSourceEventListener.class); + mediaSource.addEventListener(new Handler(Looper.myLooper()), eventListener); + ArrayList timelines = new ArrayList<>(); + mediaSource.prepareSource( + (source, timeline) -> timelines.add(timeline), + /* mediaTransferListener= */ null, + PlayerId.UNSET); + runMainLooperUntil(() -> timelines.size() == config.expectedTimelineData.size()); + + // Iterate through all periods and ads. Create and prepare them twice, because the MediaSource + // should support creating the same period more than once. + ArrayList mediaPeriods = new ArrayList<>(); + ArrayList mediaPeriodIds = new ArrayList<>(); + Timeline timeline = Iterables.getLast(timelines); + for (int i = 0; i < timeline.getPeriodCount(); i++) { + Timeline.Period period = + timeline.getPeriod(/* periodIndex= */ i, new Timeline.Period(), /* setIds= */ true); + MediaSource.MediaPeriodId mediaPeriodId = + new MediaSource.MediaPeriodId(period.uid, /* windowSequenceNumber= */ 15); + MediaPeriod mediaPeriod = + mediaSource.createPeriod(mediaPeriodId, /* allocator= */ null, /* startPositionUs= */ 0); + blockingPrepareMediaPeriod(mediaPeriod); + mediaPeriods.add(mediaPeriod); + mediaPeriodIds.add(mediaPeriodId); + + mediaPeriodId = mediaPeriodId.copyWithWindowSequenceNumber(/* windowSequenceNumber= */ 25); + mediaPeriod = + mediaSource.createPeriod(mediaPeriodId, /* allocator= */ null, /* startPositionUs= */ 0); + blockingPrepareMediaPeriod(mediaPeriod); + mediaPeriods.add(mediaPeriod); + mediaPeriodIds.add(mediaPeriodId); + + for (int j = 0; j < period.getAdGroupCount(); j++) { + for (int k = 0; k < period.getAdCountInAdGroup(j); k++) { + mediaPeriodId = + new MediaSource.MediaPeriodId( + period.uid, + /* adGroupIndex= */ j, + /* adIndexInAdGroup= */ k, + /* windowSequenceNumber= */ 35); + mediaPeriod = + mediaSource.createPeriod( + mediaPeriodId, /* allocator= */ null, /* startPositionUs= */ 0); + blockingPrepareMediaPeriod(mediaPeriod); + mediaPeriods.add(mediaPeriod); + mediaPeriodIds.add(mediaPeriodId); + + mediaPeriodId = + mediaPeriodId.copyWithWindowSequenceNumber(/* windowSequenceNumber= */ 45); + mediaPeriod = + mediaSource.createPeriod( + mediaPeriodId, /* allocator= */ null, /* startPositionUs= */ 0); + blockingPrepareMediaPeriod(mediaPeriod); + mediaPeriods.add(mediaPeriod); + mediaPeriodIds.add(mediaPeriodId); + } + } + } + // Release all periods again. + for (MediaPeriod mediaPeriod : mediaPeriods) { + mediaSource.releasePeriod(mediaPeriod); + } + + // Verify each load started and completed event is called with the correct mediaPeriodId. + for (MediaSource.MediaPeriodId mediaPeriodId : mediaPeriodIds) { + verify(eventListener) + .onLoadStarted( + /* windowIndex= */ eq(0), + /* mediaPeriodId= */ eq(mediaPeriodId), + /* loadEventInfo= */ any(), + /* mediaLoadData= */ any()); + verify(eventListener) + .onLoadCompleted( + /* windowIndex= */ eq(0), + /* mediaPeriodId= */ eq(mediaPeriodId), + /* loadEventInfo= */ any(), + /* mediaLoadData= */ any()); + } + } + + @Test + public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd() + throws Exception { + ExoPlayer player = + new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); + player.setMediaSource(config.mediaSourceSupplier.get()); + Player.Listener eventListener = mock(Player.Listener.class); + player.addListener(eventListener); + player.addAnalyticsListener(new EventLogger()); + + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + long positionAfterPrepareMs = player.getCurrentPosition(); + boolean isDynamic = player.isCurrentMediaItemDynamic(); + if (!isDynamic) { + // Dynamic streams never enter the ENDED state. + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + } + player.release(); + + ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData); + assertThat(positionAfterPrepareMs).isEqualTo(expectedData.defaultPositionMs); + if (!isDynamic) { + verify( + eventListener, + times(config.expectedAdDiscontinuities + expectedData.periodDurationsMs.length - 1)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + } + } + + @Test + public void + playback_fromSpecificPeriodPositionInFirstPeriod_startsFromCorrectPositionAndPlaysToEnd() + throws Exception { + ExoPlayer player = + new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); + MediaSource mediaSource = config.mediaSourceSupplier.get(); + player.setMediaSource(mediaSource); + Player.Listener eventListener = mock(Player.Listener.class); + player.addListener(eventListener); + player.addAnalyticsListener(new EventLogger()); + + long startWindowPositionMs = 24; + player.seekTo(startWindowPositionMs); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + long windowPositionAfterPrepareMs = player.getCurrentPosition(); + boolean isDynamic = player.isCurrentMediaItemDynamic(); + if (!isDynamic) { + // Dynamic streams never enter the ENDED state. + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + } + player.release(); + + ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData); + assertThat(windowPositionAfterPrepareMs).isEqualTo(startWindowPositionMs); + if (!isDynamic) { + verify( + eventListener, + times(expectedData.periodDurationsMs.length - 1 + config.expectedAdDiscontinuities)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + } + } + + @Test + public void + playback_fromSpecificPeriodPositionInSubsequentPeriod_startsFromCorrectPositionAndPlaysToEnd() + throws Exception { + Timeline.Period period = new Timeline.Period(); + Timeline.Window window = new Timeline.Window(); + ExoPlayer player = + new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build(); + MediaSource mediaSource = config.mediaSourceSupplier.get(); + player.setMediaSource(mediaSource); + Player.Listener eventListener = mock(Player.Listener.class); + player.addListener(eventListener); + player.addAnalyticsListener(new EventLogger()); + + ExpectedTimelineData initialTimelineData = config.expectedTimelineData.get(0); + int startPeriodIndex = max(1, initialTimelineData.periodDurationsMs.length - 2); + long startPeriodPositionMs = 24; + long startWindowPositionMs = + initialTimelineData.periodOffsetsInWindowMs[startPeriodIndex] + startPeriodPositionMs; + player.seekTo(startWindowPositionMs); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + Timeline timeline = player.getCurrentTimeline(); + long windowPositionAfterPrepareMs = player.getContentPosition(); + Pair periodPositionUs = + timeline.getPeriodPositionUs(window, period, 0, Util.msToUs(windowPositionAfterPrepareMs)); + int periodIndexAfterPrepare = timeline.getIndexOfPeriod(periodPositionUs.first); + long periodPositionAfterPrepareMs = Util.usToMs(periodPositionUs.second); + boolean isDynamic = player.isCurrentMediaItemDynamic(); + if (!isDynamic) { + // Dynamic streams never enter the ENDED state. + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + } + player.release(); + + ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData); + assertThat(periodPositionAfterPrepareMs).isEqualTo(startPeriodPositionMs); + if (timeline.getPeriod(periodIndexAfterPrepare, period).getAdGroupCount() == 0) { + assertThat(periodIndexAfterPrepare).isEqualTo(startPeriodIndex); + if (!isDynamic) { + verify(eventListener, times(expectedData.periodDurationsMs.length - startPeriodIndex - 1)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + } + } else { + // Seek beyond ad period: assert roll forward to un-played ad period. + assertThat(periodIndexAfterPrepare).isLessThan(startPeriodIndex); + verify(eventListener, atLeast(expectedData.periodDurationsMs.length - startPeriodIndex - 1)) + .onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); + timeline.getPeriod(periodIndexAfterPrepare, period); + assertThat(period.getAdGroupIndexForPositionUs(period.durationUs)) + .isNotEqualTo(C.INDEX_UNSET); + } + } + + private static void blockingPrepareMediaPeriod(MediaPeriod mediaPeriod) { + ConditionVariable mediaPeriodPrepared = new ConditionVariable(); + mediaPeriod.prepare( + new MediaPeriod.Callback() { + @Override + public void onPrepared(MediaPeriod mediaPeriod) { + mediaPeriodPrepared.open(); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + mediaPeriod.continueLoading(/* positionUs= */ 0); + } + }, + /* positionUs= */ 0); + mediaPeriodPrepared.block(); + } + + private static Supplier buildConcatenatingMediaSource( + Supplier... sources) { + return buildConcatenatingMediaSource(/* placeholderDurationMs= */ C.TIME_UNSET, sources); + } + + private static Supplier buildConcatenatingMediaSource( + long placeholderDurationMs, Supplier... sources) { + return () -> { + ConcatenatingMediaSource2.Builder builder = new ConcatenatingMediaSource2.Builder(); + builder.setMediaItem(new MediaItem.Builder().setMediaId(TEST_MEDIA_ITEM_ID).build()); + for (Supplier source : sources) { + builder.add(source.get(), placeholderDurationMs); + } + return builder.build(); + }; + } + + private static Supplier buildMediaSource( + FakeTimeline.TimelineWindowDefinition... windows) { + return buildMediaSource(/* preparationDelayCount= */ 0, windows); + } + + private static Supplier buildMediaSource( + int preparationDelayCount, FakeTimeline.TimelineWindowDefinition... windows) { + return buildMediaSource(preparationDelayCount, /* manifests= */ null, windows); + } + + private static Supplier buildMediaSource( + Object[] manifests, FakeTimeline.TimelineWindowDefinition... windows) { + return buildMediaSource(/* preparationDelayCount= */ 0, manifests, windows); + } + + private static Supplier buildMediaSource( + int preparationDelayCount, + @Nullable Object[] manifests, + FakeTimeline.TimelineWindowDefinition... windows) { + + return () -> { + // Simulate delay by repeatedly sending messages to self. This ensures that all other message + // handling trigger by source preparation finishes before the new timeline update arrives. + AtomicInteger delayCount = new AtomicInteger(10 * preparationDelayCount); + return new FakeMediaSource( + /* timeline= */ null, + new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build()) { + @Override + public synchronized void prepareSourceInternal( + @Nullable TransferListener mediaTransferListener) { + super.prepareSourceInternal(mediaTransferListener); + Handler delayHandler = new Handler(Looper.myLooper()); + Runnable handleDelay = + new Runnable() { + @Override + public void run() { + if (delayCount.getAndDecrement() == 0) { + setNewSourceInfo( + manifests != null + ? new FakeTimeline(manifests, windows) + : new FakeTimeline(windows)); + } else { + delayHandler.post(this); + } + } + }; + delayHandler.post(handleDelay); + } + }; + }; + } + + private static FakeTimeline.TimelineWindowDefinition buildWindow( + int periodCount, boolean isSeekable, boolean isDynamic, long durationMs) { + return buildWindow( + periodCount, + isSeekable, + isDynamic, + durationMs, + /* defaultPositionMs= */ 0, + /* windowOffsetInFirstPeriodMs= */ 0); + } + + private static FakeTimeline.TimelineWindowDefinition buildWindow( + int periodCount, + boolean isSeekable, + boolean isDynamic, + long durationMs, + long defaultPositionMs, + long windowOffsetInFirstPeriodMs) { + return buildWindow( + periodCount, + isSeekable, + isDynamic, + durationMs, + defaultPositionMs, + windowOffsetInFirstPeriodMs, + AdPlaybackState.NONE); + } + + private static FakeTimeline.TimelineWindowDefinition buildWindow( + int periodCount, + boolean isSeekable, + boolean isDynamic, + long durationMs, + AdPlaybackState adPlaybackState) { + return buildWindow( + periodCount, + isSeekable, + isDynamic, + durationMs, + /* defaultPositionMs= */ 0, + /* windowOffsetInFirstPeriodMs= */ 0, + adPlaybackState); + } + + private static FakeTimeline.TimelineWindowDefinition buildWindow( + int periodCount, + boolean isSeekable, + boolean isDynamic, + long durationMs, + long defaultPositionMs, + long windowOffsetInFirstPeriodMs, + AdPlaybackState adPlaybackState) { + return new FakeTimeline.TimelineWindowDefinition( + periodCount, + /* id= */ new Object(), + isSeekable, + isDynamic, + /* isLive= */ false, + /* isPlaceholder= */ false, + Util.msToUs(durationMs), + Util.msToUs(defaultPositionMs), + Util.msToUs(windowOffsetInFirstPeriodMs), + ImmutableList.of(adPlaybackState), + new MediaItem.Builder().setMediaId("").build()); + } + + private static final class TestConfig { + + public final Supplier mediaSourceSupplier; + public final ImmutableList expectedTimelineData; + + private final int expectedAdDiscontinuities; + private final String tag; + + public TestConfig( + String tag, + Supplier mediaSourceSupplier, + int expectedAdDiscontinuities, + ExpectedTimelineData... expectedTimelineData) { + this.tag = tag; + this.mediaSourceSupplier = mediaSourceSupplier; + this.expectedTimelineData = ImmutableList.copyOf(expectedTimelineData); + this.expectedAdDiscontinuities = expectedAdDiscontinuities; + } + + @Override + public String toString() { + return tag; + } + } + + private static final class ExpectedTimelineData { + + public final boolean isSeekable; + public final boolean isDynamic; + public final long defaultPositionMs; + public final long[] periodDurationsMs; + public final long[] periodOffsetsInWindowMs; + public final boolean[] periodIsPlaceholder; + public final long windowDurationMs; + public final AdPlaybackState[] adPlaybackState; + @Nullable public final Object manifest; + + public ExpectedTimelineData( + boolean isSeekable, + boolean isDynamic, + long defaultPositionMs, + long[] periodDurationsMs, + long[] periodOffsetsInWindowMs, + boolean[] periodIsPlaceholder, + long windowDurationMs, + @Nullable Object manifest) { + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + this.defaultPositionMs = defaultPositionMs; + this.periodDurationsMs = periodDurationsMs; + this.periodOffsetsInWindowMs = periodOffsetsInWindowMs; + this.periodIsPlaceholder = periodIsPlaceholder; + this.windowDurationMs = windowDurationMs; + this.adPlaybackState = new AdPlaybackState[periodDurationsMs.length]; + this.manifest = manifest; + } + + @CanIgnoreReturnValue + public ExpectedTimelineData withAdPlaybackState( + int periodIndex, AdPlaybackState adPlaybackState) { + this.adPlaybackState[periodIndex] = adPlaybackState; + return this; + } + } +} From f731a46a49e3129c30779fc3654696651ac6a713 Mon Sep 17 00:00:00 2001 From: christosts Date: Mon, 13 Feb 2023 17:15:42 +0000 Subject: [PATCH 100/104] Update release notes for ExoPlayer 2.18.3 #minor-release PiperOrigin-RevId: 509246479 (cherry picked from commit 8ff024e4c0e3d2085b2c690daddd64b53ca448ad) --- RELEASENOTES.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3bc2239e6c..f25103a3ab 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,79 @@ # Release notes +### 2.18.3 (2023-02-16) + +This release corresponds to the +[AndroidX Media3 1.0.0-rc01 release](https://github.com/androidx/media/releases/tag/1.0.0-rc01). + +* Core library: + * Tweak the renderer's decoder ordering logic to uphold the + `MediaCodecSelector`'s preferences, even if a decoder reports it may not + be able to play the media performantly. For example with default + selector, hardware decoder with only functional support will be + preferred over software decoder that fully supports the format + ([#10604](https://github.com/google/ExoPlayer/issues/10604)). + * Add `ExoPlayer.Builder.setPlaybackLooper` that sets a pre-existing + playback thread for a new ExoPlayer instance. + * Allow download manager helpers to be cleared + ([#10776](https://github.com/google/ExoPlayer/issues/10776)). + * Add parameter to `BasePlayer.seekTo` to also indicate the command used + for seeking. + * Use theme when loading drawables on API 21+ + ([#220](https://github.com/androidx/media/issues/220)). + * Add `ConcatenatingMediaSource2` that allows combining multiple media + items into a single window + ([#247](https://github.com/androidx/media/issues/247)). +* Extractors: + * Throw a `ParserException` instead of a `NullPointerException` if the + sample table (stbl) is missing a required sample description (stsd) when + parsing trak atoms. + * Correctly skip samples when seeking directly to a sync frame in fMP4 + ([#10941](https://github.com/google/ExoPlayer/issues/10941)). +* Audio: + * Use the compressed audio format bitrate to calculate the min buffer size + for `AudioTrack` in direct playbacks (passthrough). +* Text: + * Fix `TextRenderer` passing an invalid (negative) index to + `Subtitle.getEventTime` if a subtitle file contains no cues. + * SubRip: Add support for UTF-16 files if they start with a byte order + mark. +* Metadata: + * Parse multiple null-separated values from ID3 frames, as permitted by + ID3 v2.4. + * Add `MediaMetadata.mediaType` to denote the type of content or the type + of folder described by the metadata. + * Add `MediaMetadata.isBrowsable` as a replacement for + `MediaMetadata.folderType`. The folder type will be deprecated in the + next release. +* DASH: + * Add full parsing for image adaptation sets, including tile counts + ([#3752](https://github.com/google/ExoPlayer/issues/3752)). +* UI: + * Fix the deprecated + `StyledPlayerView.setControllerVisibilityListener(StyledPlayerControlView.VisibilityListener)` + to ensure visibility changes are passed to the registered listener + ([#229](https://github.com/androidx/media/issues/229)). + * Fix the ordering of the center player controls in `StyledPlayerView` + when using a right-to-left (RTL) layout + ([#227](https://github.com/androidx/media/issues/227)). +* Cast extension: + * Bump Cast SDK version to 21.2.0. +* IMA extension: + * Remove player listener of the `ImaServerSideAdInsertionMediaSource` on + the application thread to avoid threading issues. + * Add a property `focusSkipButtonWhenAvailable` to the + `ImaServerSideAdInsertionMediaSource.AdsLoader.Builder` to request + focusing the skip button on TV devices and set it to true by default. + * Add a method `focusSkipButton()` to the + `ImaServerSideAdInsertionMediaSource.AdsLoader` to programmatically + request to focus the skip button. + * Fix a bug which prevented playback from starting for a DAI stream + without any ads. + * Bump IMA SDK version to 3.29.0. +* Demo app: + * Request notification permission for download notifications at runtime + ([#10884](https://github.com/google/ExoPlayer/issues/10884)). + ### 2.18.2 (2022-11-22) This release corresponds to the From 7dbc2d4becabdfcbfadf4e540256a163ece03795 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 14 Feb 2023 13:49:22 +0000 Subject: [PATCH 101/104] Version bump for ExoPlayer 2.18.3 & media3-1.0.0-rc01 #minor-release PiperOrigin-RevId: 509501665 (cherry picked from commit b18dccde2a1984562ee42e9b058d4a23451b607d) --- .github/ISSUE_TEMPLATE/bug.yml | 1 + constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 62889430ef..7bafdbef78 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -18,6 +18,7 @@ body: label: ExoPlayer Version description: What version of ExoPlayer are you using? options: + - 2.18.3 - 2.18.2 - 2.18.1 - 2.18.0 diff --git a/constants.gradle b/constants.gradle index 3068e7646b..b25526aa41 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.18.2' - releaseVersionCode = 2_018_002 + releaseVersion = '2.18.3' + releaseVersionCode = 2_018_003 minSdkVersion = 16 appTargetSdkVersion = 33 // API version before restricting local file access. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index c2e67dfb6b..735838d253 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -27,11 +27,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.18.2"; + public static final String VERSION = "2.18.3"; /** The version of the library expressed as {@code TAG + "/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.18.2"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.18.3"; /** * The version of the library expressed as an integer, for example 1002003. @@ -41,7 +41,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 = 2_018_002; + public static final int VERSION_INT = 2_018_003; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true; From f5d442fddb745d2ddf46f24191c3c1bda9ba102c Mon Sep 17 00:00:00 2001 From: christosts Date: Wed, 15 Feb 2023 12:03:58 +0000 Subject: [PATCH 102/104] Update javadoc for ExoPlayer 2.18.3 #minor-release PiperOrigin-RevId: 509789955 (cherry picked from commit 4759e0075c4a4ac2fe8892147fed1ac341b48c0d) --- docs/doc/reference/allclasses-index.html | 2109 +++++++++-------- docs/doc/reference/allclasses.html | 8 + .../AbstractConcatenatedTimeline.html | 2 +- .../google/android/exoplayer2/BasePlayer.html | 325 ++- .../google/android/exoplayer2/C.Encoding.html | 2 +- .../com/google/android/exoplayer2/C.html | 289 +-- .../exoplayer2/ExoPlaybackException.html | 2 +- .../android/exoplayer2/ExoPlayer.Builder.html | 56 +- .../google/android/exoplayer2/ExoPlayer.html | 34 +- .../android/exoplayer2/Format.Builder.html | 50 +- .../com/google/android/exoplayer2/Format.html | 41 + .../android/exoplayer2/ForwardingPlayer.html | 39 +- .../exoplayer2/LegacyMediaPlayerWrapper.html | 14 +- ...diaItem.ClippingConfiguration.Builder.html | 4 +- .../MediaItem.LiveConfiguration.Builder.html | 4 +- .../exoplayer2/MediaMetadata.Builder.html | 77 +- .../exoplayer2/MediaMetadata.FolderType.html | 5 +- .../exoplayer2/MediaMetadata.PictureType.html | 6 +- .../android/exoplayer2/MediaMetadata.html | 825 ++++++- .../android/exoplayer2/PlaybackException.html | 27 +- .../android/exoplayer2/Player.Command.html | 45 +- .../exoplayer2/Player.Commands.Builder.html | 18 +- .../android/exoplayer2/Player.Commands.html | 2 +- .../android/exoplayer2/Player.Events.html | 12 +- .../android/exoplayer2/Player.Listener.html | 77 +- .../exoplayer2/Player.PositionInfo.html | 32 +- .../com/google/android/exoplayer2/Player.html | 675 ++++-- .../exoplayer2/RendererCapabilities.html | 12 +- .../SimpleBasePlayer.State.Builder.html | 914 ++++++- .../exoplayer2/SimpleBasePlayer.State.html | 626 ++++- .../android/exoplayer2/SimpleBasePlayer.html | 1121 +++++++-- .../android/exoplayer2/SimpleExoPlayer.html | 279 ++- .../Timeline.RemotableTimeline.html | 2 +- .../google/android/exoplayer2/Timeline.html | 42 +- .../analytics/DefaultAnalyticsCollector.html | 105 +- .../audio/Ac3Util.SyncFrameInfo.html | 37 +- ...udioSink.AudioTrackBufferSizeProvider.html | 7 +- .../DefaultAudioTrackBufferSizeProvider.html | 29 +- .../android/exoplayer2/audio/OpusUtil.html | 75 +- .../exoplayer2/ext/cast/CastPlayer.html | 181 +- ...nsertionMediaSource.AdsLoader.Builder.html | 32 +- ...rSideAdInsertionMediaSource.AdsLoader.html | 25 +- .../ext/leanback/LeanbackPlayerAdapter.html | 8 +- .../extractor/DefaultExtractorsFactory.html | 30 +- .../exoplayer2/mediacodec/MediaCodecInfo.html | 43 +- .../exoplayer2/mediacodec/MediaCodecUtil.html | 8 +- .../exoplayer2/metadata/Metadata.Entry.html | 9 +- .../metadata/flac/PictureFrame.html | 9 +- .../metadata/flac/VorbisComment.html | 9 +- .../exoplayer2/metadata/icy/IcyHeaders.html | 9 +- .../exoplayer2/metadata/icy/IcyInfo.html | 9 +- .../exoplayer2/metadata/id3/ApicFrame.html | 9 +- .../metadata/id3/TextInformationFrame.html | 71 +- .../exoplayer2/offline/DownloadService.html | 68 +- .../android/exoplayer2/package-summary.html | 83 +- .../android/exoplayer2/package-tree.html | 6 + .../robolectric/TestPlayerRunHelper.html | 48 +- .../source/CompositeMediaSource.html | 2 +- .../exoplayer2/source/ForwardingTimeline.html | 2 +- ...askingMediaSource.PlaceholderTimeline.html | 2 +- .../exoplayer2/source/MediaSource.html | 2 +- .../source/SinglePeriodTimeline.html | 2 +- .../source/ads/SinglePeriodAdTimeline.html | 2 +- .../dash/manifest/DashManifestParser.html | 31 +- .../exoplayer2/source/package-summary.html | 12 + .../exoplayer2/source/package-tree.html | 2 + .../testutil/ExoPlayerTestRunner.html | 10 +- .../testutil/FakeAudioRenderer.html | 6 +- .../FakeMediaSource.InitialTimeline.html | 2 +- .../exoplayer2/testutil/FakeTimeline.html | 2 +- .../testutil/FakeVideoRenderer.html | 6 +- .../exoplayer2/testutil/StubExoPlayer.html | 20 +- .../exoplayer2/testutil/StubPlayer.html | 261 +- .../DefaultTrackSelector.Parameters.html | 2 +- .../TrackSelectionParameters.html | 27 +- .../exoplayer2/ui/StyledPlayerView.html | 4 +- .../android/exoplayer2/util/ListenerSet.html | 56 +- .../exoplayer2/util/ParsableByteArray.html | 115 +- .../google/android/exoplayer2/util/Size.html | 16 +- .../google/android/exoplayer2/util/Util.html | 203 +- docs/doc/reference/constant-values.html | 536 +++-- docs/doc/reference/deprecated-list.html | 583 ++--- docs/doc/reference/index-all.html | 1136 ++++++++- docs/doc/reference/jquery/jszip/dist/jszip.js | 153 +- .../reference/jquery/jszip/dist/jszip.min.js | 4 +- docs/doc/reference/member-search-index.js | 2 +- docs/doc/reference/member-search-index.zip | Bin 154846 -> 157623 bytes docs/doc/reference/overview-tree.html | 8 + docs/doc/reference/package-search-index.zip | Bin 708 -> 708 bytes docs/doc/reference/type-search-index.js | 2 +- docs/doc/reference/type-search-index.zip | Bin 10921 -> 10990 bytes 91 files changed, 8919 insertions(+), 2978 deletions(-) diff --git a/docs/doc/reference/allclasses-index.html b/docs/doc/reference/allclasses-index.html index f89fe5717b..958661400f 100644 --- a/docs/doc/reference/allclasses-index.html +++ b/docs/doc/reference/allclasses-index.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":2,"i1":32,"i2":2,"i3":2,"i4":2,"i5":2,"i6":2,"i7":2,"i8":32,"i9":2,"i10":2,"i11":2,"i12":2,"i13":2,"i14":2,"i15":2,"i16":2,"i17":2,"i18":2,"i19":2,"i20":2,"i21":2,"i22":2,"i23":2,"i24":2,"i25":2,"i26":2,"i27":2,"i28":2,"i29":2,"i30":2,"i31":2,"i32":2,"i33":2,"i34":2,"i35":2,"i36":2,"i37":2,"i38":2,"i39":2,"i40":2,"i41":2,"i42":2,"i43":2,"i44":2,"i45":2,"i46":1,"i47":2,"i48":2,"i49":1,"i50":2,"i51":2,"i52":2,"i53":2,"i54":2,"i55":2,"i56":2,"i57":32,"i58":2,"i59":2,"i60":32,"i61":1,"i62":1,"i63":1,"i64":2,"i65":8,"i66":32,"i67":2,"i68":32,"i69":2,"i70":1,"i71":2,"i72":2,"i73":2,"i74":2,"i75":1,"i76":1,"i77":2,"i78":32,"i79":1,"i80":1,"i81":32,"i82":2,"i83":2,"i84":2,"i85":2,"i86":2,"i87":2,"i88":1,"i89":32,"i90":2,"i91":2,"i92":2,"i93":8,"i94":2,"i95":2,"i96":2,"i97":2,"i98":2,"i99":2,"i100":1,"i101":1,"i102":2,"i103":8,"i104":1,"i105":1,"i106":2,"i107":1,"i108":8,"i109":8,"i110":1,"i111":32,"i112":8,"i113":8,"i114":2,"i115":2,"i116":2,"i117":1,"i118":1,"i119":2,"i120":2,"i121":2,"i122":2,"i123":2,"i124":2,"i125":2,"i126":2,"i127":2,"i128":2,"i129":2,"i130":2,"i131":8,"i132":2,"i133":2,"i134":2,"i135":2,"i136":2,"i137":1,"i138":2,"i139":1,"i140":2,"i141":1,"i142":1,"i143":2,"i144":2,"i145":2,"i146":2,"i147":2,"i148":2,"i149":2,"i150":2,"i151":2,"i152":32,"i153":32,"i154":32,"i155":32,"i156":32,"i157":32,"i158":32,"i159":32,"i160":32,"i161":32,"i162":32,"i163":32,"i164":32,"i165":32,"i166":32,"i167":32,"i168":32,"i169":32,"i170":32,"i171":32,"i172":32,"i173":32,"i174":32,"i175":32,"i176":32,"i177":32,"i178":32,"i179":32,"i180":1,"i181":8,"i182":1,"i183":2,"i184":2,"i185":2,"i186":8,"i187":2,"i188":2,"i189":32,"i190":1,"i191":2,"i192":32,"i193":2,"i194":1,"i195":1,"i196":2,"i197":2,"i198":1,"i199":1,"i200":2,"i201":2,"i202":32,"i203":2,"i204":2,"i205":2,"i206":2,"i207":2,"i208":2,"i209":2,"i210":2,"i211":2,"i212":1,"i213":1,"i214":1,"i215":2,"i216":2,"i217":2,"i218":1,"i219":1,"i220":2,"i221":2,"i222":8,"i223":32,"i224":1,"i225":1,"i226":1,"i227":1,"i228":2,"i229":2,"i230":1,"i231":2,"i232":2,"i233":2,"i234":2,"i235":1,"i236":2,"i237":2,"i238":2,"i239":1,"i240":2,"i241":2,"i242":8,"i243":1,"i244":2,"i245":2,"i246":2,"i247":2,"i248":2,"i249":8,"i250":2,"i251":2,"i252":2,"i253":2,"i254":1,"i255":8,"i256":2,"i257":2,"i258":32,"i259":2,"i260":32,"i261":32,"i262":32,"i263":2,"i264":2,"i265":2,"i266":1,"i267":1,"i268":2,"i269":2,"i270":2,"i271":2,"i272":8,"i273":2,"i274":2,"i275":1,"i276":2,"i277":2,"i278":8,"i279":1,"i280":2,"i281":1,"i282":2,"i283":1,"i284":1,"i285":1,"i286":1,"i287":2,"i288":2,"i289":2,"i290":2,"i291":8,"i292":2,"i293":2,"i294":2,"i295":2,"i296":32,"i297":32,"i298":2,"i299":1,"i300":2,"i301":1,"i302":1,"i303":2,"i304":2,"i305":2,"i306":8,"i307":2,"i308":32,"i309":8,"i310":2,"i311":1,"i312":2,"i313":32,"i314":32,"i315":2,"i316":2,"i317":2,"i318":2,"i319":1,"i320":1,"i321":2,"i322":2,"i323":8,"i324":32,"i325":32,"i326":2,"i327":2,"i328":2,"i329":2,"i330":2,"i331":2,"i332":2,"i333":2,"i334":2,"i335":2,"i336":2,"i337":2,"i338":2,"i339":2,"i340":2,"i341":2,"i342":2,"i343":2,"i344":2,"i345":2,"i346":8,"i347":32,"i348":2,"i349":2,"i350":2,"i351":2,"i352":2,"i353":2,"i354":2,"i355":2,"i356":2,"i357":2,"i358":2,"i359":2,"i360":2,"i361":2,"i362":2,"i363":2,"i364":2,"i365":2,"i366":2,"i367":2,"i368":2,"i369":1,"i370":2,"i371":2,"i372":2,"i373":2,"i374":32,"i375":2,"i376":2,"i377":2,"i378":2,"i379":2,"i380":2,"i381":2,"i382":2,"i383":2,"i384":2,"i385":32,"i386":2,"i387":2,"i388":32,"i389":2,"i390":2,"i391":32,"i392":2,"i393":2,"i394":2,"i395":32,"i396":32,"i397":2,"i398":1,"i399":1,"i400":1,"i401":1,"i402":8,"i403":2,"i404":1,"i405":8,"i406":1,"i407":2,"i408":1,"i409":2,"i410":2,"i411":2,"i412":2,"i413":8,"i414":2,"i415":2,"i416":2,"i417":1,"i418":8,"i419":32,"i420":1,"i421":2,"i422":1,"i423":1,"i424":1,"i425":2,"i426":32,"i427":2,"i428":2,"i429":2,"i430":2,"i431":2,"i432":1,"i433":2,"i434":2,"i435":2,"i436":1,"i437":2,"i438":2,"i439":2,"i440":1,"i441":32,"i442":1,"i443":2,"i444":32,"i445":1,"i446":1,"i447":2,"i448":1,"i449":1,"i450":2,"i451":1,"i452":2,"i453":2,"i454":2,"i455":2,"i456":2,"i457":2,"i458":2,"i459":2,"i460":1,"i461":2,"i462":2,"i463":32,"i464":2,"i465":1,"i466":1,"i467":1,"i468":1,"i469":2,"i470":8,"i471":32,"i472":1,"i473":1,"i474":1,"i475":2,"i476":1,"i477":1,"i478":1,"i479":2,"i480":2,"i481":2,"i482":2,"i483":8,"i484":32,"i485":1,"i486":2,"i487":1,"i488":1,"i489":32,"i490":2,"i491":2,"i492":2,"i493":1,"i494":2,"i495":1,"i496":1,"i497":1,"i498":2,"i499":2,"i500":2,"i501":2,"i502":2,"i503":2,"i504":2,"i505":2,"i506":2,"i507":2,"i508":2,"i509":2,"i510":2,"i511":2,"i512":2,"i513":2,"i514":2,"i515":2,"i516":2,"i517":2,"i518":2,"i519":2,"i520":2,"i521":8,"i522":2,"i523":2,"i524":2,"i525":2,"i526":2,"i527":1,"i528":2,"i529":2,"i530":2,"i531":2,"i532":2,"i533":2,"i534":2,"i535":2,"i536":2,"i537":2,"i538":2,"i539":1,"i540":2,"i541":2,"i542":2,"i543":2,"i544":8,"i545":2,"i546":2,"i547":2,"i548":8,"i549":2,"i550":32,"i551":1,"i552":2,"i553":2,"i554":2,"i555":2,"i556":2,"i557":8,"i558":2,"i559":2,"i560":32,"i561":32,"i562":2,"i563":2,"i564":2,"i565":2,"i566":2,"i567":2,"i568":2,"i569":2,"i570":2,"i571":2,"i572":2,"i573":2,"i574":2,"i575":2,"i576":2,"i577":2,"i578":2,"i579":2,"i580":2,"i581":32,"i582":2,"i583":8,"i584":1,"i585":1,"i586":1,"i587":2,"i588":2,"i589":2,"i590":2,"i591":8,"i592":2,"i593":2,"i594":1,"i595":2,"i596":2,"i597":1,"i598":2,"i599":1,"i600":1,"i601":1,"i602":1,"i603":2,"i604":8,"i605":2,"i606":2,"i607":2,"i608":2,"i609":1,"i610":1,"i611":2,"i612":2,"i613":1,"i614":2,"i615":1,"i616":2,"i617":2,"i618":1,"i619":2,"i620":2,"i621":2,"i622":32,"i623":2,"i624":2,"i625":2,"i626":2,"i627":2,"i628":2,"i629":32,"i630":2,"i631":2,"i632":2,"i633":2,"i634":2,"i635":8,"i636":1,"i637":1,"i638":1,"i639":1,"i640":8,"i641":8,"i642":1,"i643":2,"i644":2,"i645":2,"i646":2,"i647":1,"i648":2,"i649":2,"i650":1,"i651":2,"i652":8,"i653":1,"i654":8,"i655":32,"i656":8,"i657":8,"i658":2,"i659":2,"i660":2,"i661":2,"i662":2,"i663":2,"i664":2,"i665":2,"i666":1,"i667":2,"i668":2,"i669":2,"i670":8,"i671":2,"i672":2,"i673":2,"i674":2,"i675":2,"i676":2,"i677":2,"i678":2,"i679":2,"i680":2,"i681":2,"i682":2,"i683":2,"i684":8,"i685":1,"i686":2,"i687":2,"i688":2,"i689":2,"i690":2,"i691":2,"i692":2,"i693":2,"i694":2,"i695":2,"i696":1,"i697":1,"i698":1,"i699":1,"i700":2,"i701":1,"i702":1,"i703":2,"i704":1,"i705":8,"i706":1,"i707":2,"i708":1,"i709":2,"i710":2,"i711":32,"i712":2,"i713":2,"i714":2,"i715":2,"i716":1,"i717":32,"i718":2,"i719":2,"i720":2,"i721":2,"i722":32,"i723":2,"i724":1,"i725":2,"i726":2,"i727":1,"i728":2,"i729":32,"i730":2,"i731":2,"i732":2,"i733":1,"i734":1,"i735":1,"i736":2,"i737":1,"i738":1,"i739":2,"i740":8,"i741":2,"i742":2,"i743":8,"i744":1,"i745":2,"i746":8,"i747":8,"i748":2,"i749":2,"i750":1,"i751":8,"i752":2,"i753":2,"i754":2,"i755":2,"i756":2,"i757":2,"i758":2,"i759":2,"i760":2,"i761":2,"i762":2,"i763":2,"i764":2,"i765":2,"i766":2,"i767":2,"i768":2,"i769":2,"i770":2,"i771":1,"i772":1,"i773":2,"i774":2,"i775":2,"i776":32,"i777":32,"i778":2,"i779":2,"i780":2,"i781":2,"i782":2,"i783":1,"i784":1,"i785":2,"i786":1,"i787":2,"i788":2,"i789":1,"i790":1,"i791":1,"i792":2,"i793":1,"i794":1,"i795":32,"i796":1,"i797":1,"i798":1,"i799":1,"i800":1,"i801":1,"i802":2,"i803":1,"i804":1,"i805":2,"i806":1,"i807":2,"i808":2,"i809":8,"i810":32,"i811":2,"i812":1,"i813":1,"i814":1,"i815":2,"i816":1,"i817":2,"i818":2,"i819":2,"i820":2,"i821":2,"i822":2,"i823":32,"i824":2,"i825":32,"i826":2,"i827":2,"i828":2,"i829":2,"i830":1,"i831":1,"i832":8,"i833":2,"i834":2,"i835":2,"i836":2,"i837":2,"i838":1,"i839":32,"i840":2,"i841":2,"i842":2,"i843":32,"i844":2,"i845":2,"i846":2,"i847":2,"i848":2,"i849":2,"i850":8,"i851":2,"i852":2,"i853":2,"i854":2,"i855":2,"i856":2,"i857":8,"i858":2,"i859":1,"i860":2,"i861":2,"i862":2,"i863":2,"i864":2,"i865":2,"i866":2,"i867":2,"i868":2,"i869":2,"i870":8,"i871":32,"i872":2,"i873":2,"i874":1,"i875":1,"i876":2,"i877":2,"i878":2,"i879":2,"i880":2,"i881":1,"i882":1,"i883":32,"i884":2,"i885":2,"i886":32,"i887":32,"i888":2,"i889":1,"i890":32,"i891":32,"i892":32,"i893":2,"i894":32,"i895":32,"i896":32,"i897":2,"i898":1,"i899":1,"i900":2,"i901":1,"i902":2,"i903":2,"i904":1,"i905":1,"i906":2,"i907":2,"i908":1,"i909":1,"i910":1,"i911":32,"i912":32,"i913":2,"i914":32,"i915":2,"i916":2,"i917":32,"i918":2,"i919":2,"i920":2,"i921":2,"i922":8,"i923":2,"i924":2,"i925":2,"i926":2,"i927":2,"i928":1,"i929":1,"i930":2,"i931":2,"i932":2,"i933":2,"i934":2,"i935":2,"i936":2,"i937":2,"i938":2,"i939":2,"i940":8,"i941":1,"i942":32,"i943":32,"i944":1,"i945":1,"i946":32,"i947":32,"i948":32,"i949":32,"i950":32,"i951":32,"i952":2,"i953":1,"i954":2,"i955":2,"i956":32,"i957":2,"i958":2,"i959":2,"i960":2,"i961":32,"i962":2,"i963":1,"i964":2,"i965":2,"i966":1,"i967":2,"i968":2,"i969":2,"i970":1,"i971":2,"i972":2,"i973":2,"i974":2,"i975":2,"i976":2,"i977":2,"i978":2,"i979":1,"i980":1,"i981":2,"i982":2,"i983":2,"i984":2,"i985":8,"i986":2,"i987":2,"i988":2,"i989":1,"i990":8,"i991":1,"i992":32,"i993":32,"i994":2,"i995":2,"i996":1,"i997":1,"i998":2,"i999":1,"i1000":2,"i1001":2,"i1002":2,"i1003":2,"i1004":2,"i1005":2,"i1006":2,"i1007":2,"i1008":2,"i1009":2,"i1010":2,"i1011":2,"i1012":2,"i1013":1,"i1014":1,"i1015":2,"i1016":1,"i1017":2,"i1018":2,"i1019":1,"i1020":2,"i1021":1,"i1022":1,"i1023":2,"i1024":1,"i1025":2,"i1026":1,"i1027":1,"i1028":1,"i1029":1,"i1030":2,"i1031":2,"i1032":1,"i1033":2,"i1034":2,"i1035":2,"i1036":2,"i1037":2,"i1038":2,"i1039":2,"i1040":2,"i1041":2,"i1042":2,"i1043":2,"i1044":2,"i1045":2,"i1046":2,"i1047":2,"i1048":2,"i1049":2,"i1050":2,"i1051":2,"i1052":2,"i1053":2,"i1054":2,"i1055":2,"i1056":2,"i1057":2,"i1058":2,"i1059":2,"i1060":2,"i1061":1,"i1062":2,"i1063":2,"i1064":1,"i1065":1,"i1066":1,"i1067":1,"i1068":1,"i1069":1,"i1070":1,"i1071":1,"i1072":1,"i1073":2,"i1074":2,"i1075":1,"i1076":2,"i1077":2,"i1078":2,"i1079":2,"i1080":2,"i1081":2,"i1082":2,"i1083":2,"i1084":2,"i1085":1,"i1086":1,"i1087":2,"i1088":2,"i1089":2,"i1090":2,"i1091":2,"i1092":8,"i1093":2,"i1094":2,"i1095":2,"i1096":2,"i1097":2,"i1098":2,"i1099":2,"i1100":2,"i1101":2,"i1102":2,"i1103":2,"i1104":1,"i1105":1,"i1106":1,"i1107":2,"i1108":1,"i1109":1,"i1110":32,"i1111":2,"i1112":1,"i1113":1,"i1114":8,"i1115":1,"i1116":2,"i1117":2,"i1118":2,"i1119":2,"i1120":32,"i1121":2,"i1122":2,"i1123":2,"i1124":2,"i1125":2,"i1126":1,"i1127":2,"i1128":2,"i1129":2,"i1130":2,"i1131":2,"i1132":2,"i1133":2,"i1134":32,"i1135":2,"i1136":32,"i1137":32,"i1138":2,"i1139":1,"i1140":2,"i1141":2,"i1142":2,"i1143":1,"i1144":1,"i1145":2,"i1146":2,"i1147":2,"i1148":2,"i1149":2,"i1150":2,"i1151":2,"i1152":1,"i1153":2,"i1154":1,"i1155":2,"i1156":2,"i1157":2,"i1158":2,"i1159":1,"i1160":2,"i1161":2,"i1162":32,"i1163":2,"i1164":2,"i1165":2,"i1166":1,"i1167":1,"i1168":2,"i1169":32,"i1170":2,"i1171":2,"i1172":1,"i1173":32,"i1174":2,"i1175":2,"i1176":1,"i1177":2,"i1178":2,"i1179":2,"i1180":2,"i1181":1,"i1182":2,"i1183":1,"i1184":2,"i1185":1,"i1186":2,"i1187":1,"i1188":8,"i1189":32,"i1190":2,"i1191":2,"i1192":2,"i1193":2,"i1194":2,"i1195":2,"i1196":1,"i1197":32,"i1198":2,"i1199":2,"i1200":32,"i1201":1,"i1202":2,"i1203":2,"i1204":1,"i1205":32,"i1206":2,"i1207":2,"i1208":2,"i1209":2,"i1210":2,"i1211":8,"i1212":32,"i1213":8,"i1214":8,"i1215":32,"i1216":2,"i1217":2,"i1218":2,"i1219":2,"i1220":2,"i1221":2,"i1222":2,"i1223":2,"i1224":1,"i1225":2,"i1226":32,"i1227":2,"i1228":1,"i1229":2,"i1230":1,"i1231":2,"i1232":2,"i1233":2,"i1234":2,"i1235":2,"i1236":2,"i1237":2,"i1238":2,"i1239":2,"i1240":2,"i1241":8,"i1242":2,"i1243":2,"i1244":2,"i1245":2,"i1246":2,"i1247":2,"i1248":2,"i1249":32,"i1250":32,"i1251":2,"i1252":2,"i1253":2,"i1254":2,"i1255":2,"i1256":2,"i1257":2,"i1258":2,"i1259":2,"i1260":1,"i1261":2}; +var data = {"i0":2,"i1":32,"i2":2,"i3":2,"i4":2,"i5":2,"i6":2,"i7":2,"i8":32,"i9":2,"i10":2,"i11":2,"i12":2,"i13":2,"i14":2,"i15":2,"i16":2,"i17":2,"i18":2,"i19":2,"i20":2,"i21":2,"i22":2,"i23":2,"i24":2,"i25":2,"i26":2,"i27":2,"i28":2,"i29":2,"i30":2,"i31":2,"i32":2,"i33":2,"i34":2,"i35":2,"i36":2,"i37":2,"i38":2,"i39":2,"i40":2,"i41":2,"i42":2,"i43":2,"i44":2,"i45":2,"i46":1,"i47":2,"i48":2,"i49":1,"i50":2,"i51":2,"i52":2,"i53":2,"i54":2,"i55":2,"i56":2,"i57":32,"i58":2,"i59":2,"i60":32,"i61":1,"i62":1,"i63":1,"i64":2,"i65":8,"i66":32,"i67":2,"i68":32,"i69":2,"i70":1,"i71":2,"i72":2,"i73":2,"i74":2,"i75":1,"i76":1,"i77":2,"i78":32,"i79":1,"i80":1,"i81":32,"i82":2,"i83":2,"i84":2,"i85":2,"i86":2,"i87":2,"i88":1,"i89":32,"i90":2,"i91":2,"i92":2,"i93":8,"i94":2,"i95":2,"i96":2,"i97":2,"i98":2,"i99":2,"i100":1,"i101":1,"i102":2,"i103":8,"i104":1,"i105":1,"i106":2,"i107":1,"i108":8,"i109":8,"i110":1,"i111":32,"i112":8,"i113":8,"i114":2,"i115":2,"i116":2,"i117":1,"i118":1,"i119":2,"i120":2,"i121":2,"i122":2,"i123":2,"i124":2,"i125":2,"i126":2,"i127":2,"i128":2,"i129":2,"i130":2,"i131":8,"i132":2,"i133":2,"i134":2,"i135":2,"i136":2,"i137":1,"i138":2,"i139":1,"i140":2,"i141":1,"i142":1,"i143":2,"i144":2,"i145":2,"i146":2,"i147":2,"i148":2,"i149":2,"i150":2,"i151":2,"i152":32,"i153":32,"i154":32,"i155":32,"i156":32,"i157":32,"i158":32,"i159":32,"i160":32,"i161":32,"i162":32,"i163":32,"i164":32,"i165":32,"i166":32,"i167":32,"i168":32,"i169":32,"i170":32,"i171":32,"i172":32,"i173":32,"i174":32,"i175":32,"i176":32,"i177":32,"i178":32,"i179":32,"i180":1,"i181":8,"i182":1,"i183":2,"i184":2,"i185":2,"i186":8,"i187":2,"i188":2,"i189":32,"i190":1,"i191":2,"i192":32,"i193":2,"i194":1,"i195":1,"i196":2,"i197":2,"i198":1,"i199":1,"i200":2,"i201":2,"i202":32,"i203":2,"i204":2,"i205":2,"i206":2,"i207":2,"i208":2,"i209":2,"i210":2,"i211":2,"i212":1,"i213":1,"i214":1,"i215":2,"i216":2,"i217":2,"i218":1,"i219":1,"i220":2,"i221":2,"i222":8,"i223":32,"i224":1,"i225":1,"i226":1,"i227":1,"i228":2,"i229":2,"i230":1,"i231":2,"i232":2,"i233":2,"i234":2,"i235":1,"i236":2,"i237":2,"i238":2,"i239":2,"i240":2,"i241":1,"i242":2,"i243":2,"i244":8,"i245":1,"i246":2,"i247":2,"i248":2,"i249":2,"i250":2,"i251":8,"i252":2,"i253":2,"i254":2,"i255":2,"i256":1,"i257":8,"i258":2,"i259":2,"i260":32,"i261":2,"i262":32,"i263":32,"i264":32,"i265":2,"i266":2,"i267":2,"i268":1,"i269":1,"i270":2,"i271":2,"i272":2,"i273":2,"i274":8,"i275":2,"i276":2,"i277":1,"i278":2,"i279":2,"i280":8,"i281":1,"i282":2,"i283":1,"i284":2,"i285":1,"i286":1,"i287":1,"i288":1,"i289":2,"i290":2,"i291":2,"i292":2,"i293":8,"i294":2,"i295":2,"i296":2,"i297":2,"i298":32,"i299":32,"i300":2,"i301":1,"i302":2,"i303":1,"i304":1,"i305":2,"i306":2,"i307":2,"i308":8,"i309":2,"i310":32,"i311":8,"i312":2,"i313":1,"i314":2,"i315":32,"i316":32,"i317":2,"i318":2,"i319":2,"i320":2,"i321":1,"i322":1,"i323":2,"i324":2,"i325":8,"i326":32,"i327":32,"i328":2,"i329":2,"i330":2,"i331":2,"i332":2,"i333":2,"i334":2,"i335":2,"i336":2,"i337":2,"i338":2,"i339":2,"i340":2,"i341":2,"i342":2,"i343":2,"i344":2,"i345":2,"i346":2,"i347":2,"i348":8,"i349":32,"i350":2,"i351":2,"i352":2,"i353":2,"i354":2,"i355":2,"i356":2,"i357":2,"i358":2,"i359":2,"i360":2,"i361":2,"i362":2,"i363":2,"i364":2,"i365":2,"i366":2,"i367":2,"i368":2,"i369":2,"i370":2,"i371":1,"i372":2,"i373":2,"i374":2,"i375":2,"i376":32,"i377":2,"i378":2,"i379":2,"i380":2,"i381":2,"i382":2,"i383":2,"i384":2,"i385":2,"i386":2,"i387":32,"i388":2,"i389":2,"i390":32,"i391":2,"i392":2,"i393":32,"i394":2,"i395":2,"i396":2,"i397":32,"i398":32,"i399":2,"i400":1,"i401":1,"i402":1,"i403":1,"i404":8,"i405":2,"i406":1,"i407":8,"i408":1,"i409":2,"i410":1,"i411":2,"i412":2,"i413":2,"i414":2,"i415":8,"i416":2,"i417":2,"i418":2,"i419":1,"i420":8,"i421":32,"i422":1,"i423":2,"i424":1,"i425":1,"i426":1,"i427":2,"i428":32,"i429":2,"i430":2,"i431":2,"i432":2,"i433":2,"i434":1,"i435":2,"i436":2,"i437":2,"i438":1,"i439":2,"i440":2,"i441":2,"i442":1,"i443":32,"i444":1,"i445":2,"i446":32,"i447":1,"i448":1,"i449":2,"i450":1,"i451":1,"i452":2,"i453":1,"i454":2,"i455":2,"i456":2,"i457":2,"i458":2,"i459":2,"i460":2,"i461":2,"i462":1,"i463":2,"i464":2,"i465":32,"i466":2,"i467":1,"i468":1,"i469":1,"i470":1,"i471":2,"i472":8,"i473":32,"i474":1,"i475":1,"i476":1,"i477":2,"i478":1,"i479":1,"i480":1,"i481":2,"i482":2,"i483":2,"i484":2,"i485":8,"i486":32,"i487":1,"i488":2,"i489":1,"i490":1,"i491":32,"i492":2,"i493":2,"i494":2,"i495":1,"i496":2,"i497":1,"i498":1,"i499":1,"i500":2,"i501":2,"i502":2,"i503":2,"i504":2,"i505":2,"i506":2,"i507":2,"i508":2,"i509":2,"i510":2,"i511":2,"i512":2,"i513":2,"i514":2,"i515":2,"i516":2,"i517":2,"i518":2,"i519":2,"i520":2,"i521":2,"i522":2,"i523":8,"i524":2,"i525":2,"i526":2,"i527":2,"i528":2,"i529":1,"i530":2,"i531":2,"i532":2,"i533":2,"i534":2,"i535":2,"i536":2,"i537":2,"i538":2,"i539":2,"i540":2,"i541":1,"i542":2,"i543":2,"i544":2,"i545":2,"i546":8,"i547":2,"i548":2,"i549":2,"i550":8,"i551":2,"i552":32,"i553":1,"i554":2,"i555":2,"i556":2,"i557":2,"i558":2,"i559":8,"i560":2,"i561":2,"i562":32,"i563":32,"i564":2,"i565":2,"i566":2,"i567":2,"i568":2,"i569":2,"i570":2,"i571":2,"i572":2,"i573":2,"i574":2,"i575":2,"i576":2,"i577":2,"i578":2,"i579":2,"i580":2,"i581":2,"i582":2,"i583":32,"i584":2,"i585":8,"i586":1,"i587":1,"i588":1,"i589":2,"i590":2,"i591":2,"i592":2,"i593":8,"i594":2,"i595":2,"i596":1,"i597":2,"i598":2,"i599":1,"i600":2,"i601":1,"i602":1,"i603":1,"i604":1,"i605":2,"i606":8,"i607":2,"i608":2,"i609":2,"i610":2,"i611":1,"i612":1,"i613":2,"i614":2,"i615":1,"i616":2,"i617":1,"i618":2,"i619":2,"i620":1,"i621":2,"i622":2,"i623":2,"i624":32,"i625":2,"i626":2,"i627":2,"i628":2,"i629":2,"i630":2,"i631":32,"i632":2,"i633":2,"i634":2,"i635":2,"i636":2,"i637":8,"i638":1,"i639":1,"i640":1,"i641":1,"i642":8,"i643":8,"i644":1,"i645":2,"i646":2,"i647":2,"i648":2,"i649":1,"i650":2,"i651":2,"i652":1,"i653":2,"i654":8,"i655":1,"i656":8,"i657":32,"i658":8,"i659":8,"i660":2,"i661":2,"i662":2,"i663":2,"i664":2,"i665":2,"i666":2,"i667":2,"i668":1,"i669":2,"i670":2,"i671":2,"i672":8,"i673":2,"i674":2,"i675":2,"i676":2,"i677":2,"i678":2,"i679":2,"i680":2,"i681":2,"i682":2,"i683":2,"i684":2,"i685":2,"i686":8,"i687":1,"i688":2,"i689":2,"i690":2,"i691":2,"i692":2,"i693":2,"i694":2,"i695":2,"i696":2,"i697":2,"i698":1,"i699":1,"i700":1,"i701":1,"i702":2,"i703":1,"i704":1,"i705":2,"i706":1,"i707":8,"i708":1,"i709":2,"i710":1,"i711":2,"i712":2,"i713":32,"i714":2,"i715":2,"i716":2,"i717":2,"i718":1,"i719":32,"i720":2,"i721":2,"i722":2,"i723":2,"i724":32,"i725":2,"i726":1,"i727":2,"i728":2,"i729":1,"i730":2,"i731":32,"i732":2,"i733":2,"i734":2,"i735":1,"i736":1,"i737":1,"i738":2,"i739":1,"i740":1,"i741":2,"i742":8,"i743":2,"i744":2,"i745":8,"i746":1,"i747":2,"i748":8,"i749":8,"i750":2,"i751":2,"i752":1,"i753":8,"i754":2,"i755":2,"i756":2,"i757":2,"i758":2,"i759":2,"i760":2,"i761":2,"i762":2,"i763":2,"i764":2,"i765":2,"i766":2,"i767":2,"i768":2,"i769":2,"i770":2,"i771":2,"i772":2,"i773":1,"i774":1,"i775":2,"i776":2,"i777":2,"i778":32,"i779":32,"i780":32,"i781":2,"i782":2,"i783":2,"i784":2,"i785":2,"i786":1,"i787":1,"i788":2,"i789":1,"i790":2,"i791":2,"i792":1,"i793":1,"i794":1,"i795":2,"i796":1,"i797":1,"i798":32,"i799":1,"i800":1,"i801":1,"i802":1,"i803":1,"i804":1,"i805":2,"i806":1,"i807":1,"i808":2,"i809":1,"i810":2,"i811":2,"i812":8,"i813":32,"i814":2,"i815":1,"i816":1,"i817":1,"i818":2,"i819":1,"i820":2,"i821":2,"i822":2,"i823":2,"i824":2,"i825":2,"i826":32,"i827":2,"i828":32,"i829":2,"i830":2,"i831":2,"i832":2,"i833":1,"i834":1,"i835":8,"i836":2,"i837":2,"i838":2,"i839":2,"i840":2,"i841":1,"i842":32,"i843":2,"i844":2,"i845":2,"i846":32,"i847":2,"i848":2,"i849":2,"i850":2,"i851":2,"i852":2,"i853":8,"i854":2,"i855":2,"i856":2,"i857":2,"i858":2,"i859":2,"i860":8,"i861":2,"i862":1,"i863":2,"i864":2,"i865":2,"i866":2,"i867":2,"i868":2,"i869":2,"i870":2,"i871":2,"i872":2,"i873":8,"i874":32,"i875":2,"i876":2,"i877":1,"i878":1,"i879":2,"i880":2,"i881":2,"i882":2,"i883":2,"i884":1,"i885":1,"i886":32,"i887":2,"i888":2,"i889":32,"i890":32,"i891":2,"i892":1,"i893":32,"i894":32,"i895":32,"i896":2,"i897":32,"i898":32,"i899":32,"i900":2,"i901":1,"i902":1,"i903":2,"i904":1,"i905":2,"i906":2,"i907":1,"i908":1,"i909":2,"i910":2,"i911":1,"i912":1,"i913":1,"i914":32,"i915":32,"i916":2,"i917":32,"i918":2,"i919":2,"i920":32,"i921":2,"i922":2,"i923":2,"i924":2,"i925":8,"i926":2,"i927":2,"i928":2,"i929":2,"i930":2,"i931":1,"i932":1,"i933":2,"i934":2,"i935":2,"i936":2,"i937":2,"i938":2,"i939":2,"i940":2,"i941":2,"i942":2,"i943":8,"i944":1,"i945":32,"i946":32,"i947":1,"i948":1,"i949":32,"i950":32,"i951":32,"i952":32,"i953":32,"i954":32,"i955":2,"i956":1,"i957":2,"i958":2,"i959":32,"i960":2,"i961":2,"i962":2,"i963":2,"i964":32,"i965":2,"i966":1,"i967":2,"i968":2,"i969":1,"i970":2,"i971":2,"i972":2,"i973":1,"i974":2,"i975":2,"i976":2,"i977":2,"i978":2,"i979":2,"i980":2,"i981":2,"i982":1,"i983":1,"i984":2,"i985":2,"i986":2,"i987":2,"i988":8,"i989":2,"i990":2,"i991":2,"i992":1,"i993":8,"i994":1,"i995":32,"i996":32,"i997":2,"i998":2,"i999":1,"i1000":1,"i1001":2,"i1002":1,"i1003":2,"i1004":2,"i1005":2,"i1006":2,"i1007":2,"i1008":2,"i1009":2,"i1010":2,"i1011":2,"i1012":2,"i1013":2,"i1014":2,"i1015":2,"i1016":1,"i1017":1,"i1018":2,"i1019":1,"i1020":2,"i1021":2,"i1022":1,"i1023":2,"i1024":1,"i1025":1,"i1026":2,"i1027":1,"i1028":2,"i1029":1,"i1030":1,"i1031":1,"i1032":1,"i1033":2,"i1034":2,"i1035":1,"i1036":2,"i1037":2,"i1038":2,"i1039":2,"i1040":2,"i1041":2,"i1042":2,"i1043":2,"i1044":2,"i1045":2,"i1046":1,"i1047":2,"i1048":2,"i1049":2,"i1050":2,"i1051":2,"i1052":2,"i1053":2,"i1054":2,"i1055":2,"i1056":2,"i1057":2,"i1058":2,"i1059":2,"i1060":2,"i1061":2,"i1062":2,"i1063":2,"i1064":2,"i1065":2,"i1066":2,"i1067":2,"i1068":2,"i1069":1,"i1070":2,"i1071":2,"i1072":1,"i1073":1,"i1074":1,"i1075":1,"i1076":1,"i1077":1,"i1078":1,"i1079":1,"i1080":1,"i1081":2,"i1082":2,"i1083":1,"i1084":2,"i1085":2,"i1086":2,"i1087":2,"i1088":2,"i1089":2,"i1090":2,"i1091":2,"i1092":2,"i1093":1,"i1094":1,"i1095":2,"i1096":2,"i1097":2,"i1098":2,"i1099":2,"i1100":8,"i1101":2,"i1102":2,"i1103":2,"i1104":2,"i1105":2,"i1106":2,"i1107":2,"i1108":2,"i1109":2,"i1110":2,"i1111":2,"i1112":1,"i1113":1,"i1114":1,"i1115":2,"i1116":1,"i1117":1,"i1118":32,"i1119":2,"i1120":1,"i1121":1,"i1122":8,"i1123":1,"i1124":2,"i1125":2,"i1126":2,"i1127":2,"i1128":32,"i1129":2,"i1130":2,"i1131":2,"i1132":2,"i1133":2,"i1134":1,"i1135":2,"i1136":2,"i1137":2,"i1138":2,"i1139":2,"i1140":2,"i1141":2,"i1142":32,"i1143":2,"i1144":32,"i1145":32,"i1146":2,"i1147":1,"i1148":2,"i1149":2,"i1150":2,"i1151":1,"i1152":1,"i1153":2,"i1154":2,"i1155":2,"i1156":2,"i1157":2,"i1158":2,"i1159":2,"i1160":1,"i1161":2,"i1162":1,"i1163":2,"i1164":2,"i1165":2,"i1166":2,"i1167":1,"i1168":2,"i1169":2,"i1170":32,"i1171":2,"i1172":2,"i1173":2,"i1174":1,"i1175":1,"i1176":2,"i1177":32,"i1178":2,"i1179":2,"i1180":1,"i1181":32,"i1182":2,"i1183":2,"i1184":1,"i1185":2,"i1186":2,"i1187":2,"i1188":2,"i1189":1,"i1190":2,"i1191":1,"i1192":2,"i1193":1,"i1194":2,"i1195":1,"i1196":8,"i1197":32,"i1198":2,"i1199":2,"i1200":2,"i1201":2,"i1202":2,"i1203":2,"i1204":1,"i1205":32,"i1206":2,"i1207":2,"i1208":32,"i1209":1,"i1210":2,"i1211":2,"i1212":1,"i1213":32,"i1214":2,"i1215":2,"i1216":2,"i1217":2,"i1218":2,"i1219":8,"i1220":32,"i1221":8,"i1222":8,"i1223":32,"i1224":2,"i1225":2,"i1226":2,"i1227":2,"i1228":2,"i1229":2,"i1230":2,"i1231":2,"i1232":1,"i1233":2,"i1234":32,"i1235":2,"i1236":1,"i1237":2,"i1238":1,"i1239":2,"i1240":2,"i1241":2,"i1242":2,"i1243":2,"i1244":2,"i1245":2,"i1246":2,"i1247":2,"i1248":2,"i1249":8,"i1250":2,"i1251":2,"i1252":2,"i1253":2,"i1254":2,"i1255":2,"i1256":2,"i1257":32,"i1258":32,"i1259":2,"i1260":2,"i1261":2,"i1262":2,"i1263":2,"i1264":2,"i1265":2,"i1266":2,"i1267":2,"i1268":1,"i1269":2}; var tabs = {65535:["t0","All Classes"],1:["t1","Interface Summary"],2:["t2","Class Summary"],8:["t4","Exception Summary"],32:["t6","Annotation Types Summary"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -1556,706 +1556,718 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +ConcatenatingMediaSource2 + +
      Concatenates multiple MediaSources, combining everything in one single Timeline.Window.
      + + + +ConcatenatingMediaSource2.Builder + +
      A builder for ConcatenatingMediaSource2 instances.
      + + + ConditionVariable
      An interruptible condition variable.
      - + ConstantBitrateSeekMap
      A SeekMap implementation that assumes the stream has a constant bitrate and consists of multiple independent frames of the same size.
      - + Consumer<T>
      Represents an operation that accepts a single input argument and returns no result.
      - + ContainerMediaChunk
      A BaseMediaChunk that uses an Extractor to decode sample data.
      - + ContentDataSource
      A DataSource for reading from a content URI.
      - + ContentDataSource.ContentDataSourceException
      Thrown when an IOException is encountered reading from a content URI.
      - + ContentMetadata
      Interface for an immutable snapshot of keyed metadata.
      - + ContentMetadataMutations
      Defines multiple mutations on metadata value which are applied atomically.
      - + Contrast
      A GlEffect to control the contrast of video frames.
      - + CopyOnWriteMultiset<E>
      An unordered collection of elements that allows duplicates, but also allows access to a set of unique elements.
      - + CronetDataSource
      DataSource without intermediate buffer based on Cronet API set using UrlRequest.
      - + CronetDataSource.Factory - + CronetDataSource.OpenException
      Thrown when an error is encountered when trying to open a CronetDataSource.
      - + CronetDataSourceFactory Deprecated. - + CronetEngineWrapper Deprecated.
      Use CronetEngine directly.
      - + CronetUtil
      Cronet utility methods.
      - + Crop
      Specifies a crop to apply in the vertex shader.
      - + CryptoConfig
      Configuration for a decoder to allow it to decode encrypted media data.
      - + CryptoException
      Thrown when a non-platform component fails to decrypt data.
      - + CryptoInfo
      Metadata describing the structure of an encrypted input sample.
      - + Cue
      Contains information about a specific cue, including textual content and formatting data.
      - + Cue.AnchorType
      The type of anchor, which may be unset.
      - + Cue.Builder
      A builder for Cue objects.
      - + Cue.LineType
      The type of line, which may be unset.
      - + Cue.TextSizeType
      The type of default text size for this cue, which may be unset.
      - + Cue.VerticalType
      The type of vertical layout for this cue, which may be unset (i.e.
      - + CueDecoder
      Decodes data encoded by CueEncoder.
      - + CueEncoder
      Encodes data that can be decoded by CueDecoder.
      - + CueGroup
      Class to represent the state of active Cues at a particular time.
      - + DashChunkSource
      A ChunkSource for DASH streams.
      - + DashChunkSource.Factory
      Factory for DashChunkSources.
      - + DashDownloader
      A downloader for DASH streams.
      - + DashManifest
      Represents a DASH media presentation description (mpd), as defined by ISO/IEC 23009-1:2014 Section 5.3.1.2.
      - + DashManifestParser
      A parser of media presentation description files.
      - + DashManifestParser.RepresentationInfo
      A parsed Representation element.
      - + DashManifestStaleException
      Thrown when a live playback's manifest is stale and a new manifest could not be loaded.
      - + DashMediaSource
      A DASH MediaSource.
      - + DashMediaSource.Factory
      Factory for DashMediaSources.
      - + DashSegmentIndex
      Indexes the segments within a media stream.
      - + DashUtil
      Utility methods for DASH streams.
      - + DashWrappingSegmentIndex
      An implementation of DashSegmentIndex that wraps a ChunkIndex parsed from a media stream.
      - + DatabaseIOException
      An IOException whose cause is an SQLException.
      - + DatabaseProvider
      Provides SQLiteDatabase instances to media library components, which may read and write tables prefixed with DatabaseProvider.TABLE_PREFIX.
      - + DataChunk
      A base class for Chunk implementations where the data should be loaded into a byte[] before being consumed.
      - + DataReader
      Reads bytes from a data stream.
      - + DataSchemeDataSource
      A DataSource for reading data URLs, as defined by RFC 2397.
      - + DataSink
      A component to which streams of data can be written.
      - + DataSink.Factory
      A factory for DataSink instances.
      - + DataSource
      Reads data from URI-identified resources.
      - + DataSource.Factory
      A factory for DataSource instances.
      - + DataSourceContractTest
      A collection of contract tests for DataSource implementations.
      - + DataSourceContractTest.FakeTransferListener
      A TransferListener that only keeps track of the transferred bytes.
      - + DataSourceContractTest.TestResource
      Information about a resource that can be used to test the DataSource instance.
      - + DataSourceContractTest.TestResource.Builder - + DataSourceException
      Used to specify reason of a DataSource error.
      - + DataSourceInputStream
      Allows data corresponding to a given DataSpec to be read from a DataSource and consumed through an InputStream.
      - + DataSourceUtil
      Utility methods for DataSource.
      - + DataSpec
      Defines a region of data in a resource.
      - + DataSpec.Builder
      Builds DataSpec instances.
      - + DataSpec.Flags
      The flags that apply to any request for data.
      - + DataSpec.HttpMethod
      HTTP methods supported by ExoPlayer HttpDataSources.
      - + DebugTextViewHelper
      A helper class for periodically updating a TextView with debug information obtained from an ExoPlayer.
      - + DebugViewProvider
      Provider for views to show diagnostic information during a transformation, for debugging.
      - + DecodeOneFrameUtil
      Utilities for decoding a frame for tests.
      - + DecodeOneFrameUtil.Listener
      Listener for decoding events.
      - + Decoder<I,​O,​E extends DecoderException>
      A media decoder.
      - + DecoderAudioRenderer<T extends Decoder<DecoderInputBuffer,​? extends SimpleDecoderOutputBuffer,​? extends DecoderException>>
      Decodes and renders audio using a Decoder.
      - + DecoderCounters
      Maintains decoder event counts, for debugging purposes only.
      - + DecoderCountersUtil
      Assertions for DecoderCounters.
      - + DecoderException
      Thrown when a Decoder error occurs.
      - + DecoderInputBuffer
      Holds input for a decoder.
      - + DecoderInputBuffer.BufferReplacementMode
      The buffer replacement mode.
      - + DecoderInputBuffer.InsufficientCapacityException
      Thrown when an attempt is made to write into a DecoderInputBuffer whose DecoderInputBuffer.bufferReplacementMode is DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED and who DecoderInputBuffer.data capacity is smaller than required.
      - + DecoderOutputBuffer
      Output buffer decoded by a Decoder.
      - + DecoderOutputBuffer.Owner<S extends DecoderOutputBuffer>
      Buffer owner.
      - + DecoderReuseEvaluation
      The result of an evaluation to determine whether a decoder can be reused for a new input format.
      - + DecoderReuseEvaluation.DecoderDiscardReasons
      Possible reasons why reuse is not possible.
      - + DecoderReuseEvaluation.DecoderReuseResult
      Possible outcomes of the evaluation.
      - + DecoderVideoRenderer
      Decodes and renders video using a Decoder.
      - + DefaultAllocator
      Default implementation of Allocator.
      - + DefaultAnalyticsCollector
      Data collector that forwards analytics events to AnalyticsListeners.
      - + DefaultAudioSink
      Plays audio data.
      - + DefaultAudioSink.AudioProcessorChain Deprecated. - + DefaultAudioSink.AudioTrackBufferSizeProvider
      Provides the buffer size to use when creating an AudioTrack.
      - + DefaultAudioSink.Builder
      A builder to create DefaultAudioSink instances.
      - + DefaultAudioSink.DefaultAudioProcessorChain
      The default audio processor chain, which applies a (possibly empty) chain of user-defined audio processors followed by SilenceSkippingAudioProcessor and SonicAudioProcessor.
      - + DefaultAudioSink.InvalidAudioTrackTimestampException
      Thrown when the audio track has provided a spurious timestamp, if DefaultAudioSink.failOnSpuriousAudioTimestamp is set.
      - + DefaultAudioSink.OffloadMode
      Audio offload mode configuration.
      - + DefaultAudioSink.OutputMode
      Output mode of the audio sink.
      - + DefaultAudioTrackBufferSizeProvider
      Provide the buffer size to use when creating an AudioTrack.
      - + DefaultAudioTrackBufferSizeProvider.Builder
      A builder to create DefaultAudioTrackBufferSizeProvider instances.
      - + DefaultBandwidthMeter
      Estimates bandwidth by listening to data transfers.
      - + DefaultBandwidthMeter.Builder
      Builder for a bandwidth meter.
      - + DefaultCastOptionsProvider
      A convenience OptionsProvider to target the default cast receiver app.
      - + DefaultCodec
      A default Codec implementation that uses MediaCodec.
      - + DefaultCompositeSequenceableLoaderFactory
      Default implementation of CompositeSequenceableLoaderFactory.
      - + DefaultContentMetadata
      Default implementation of ContentMetadata.
      - + DefaultDashChunkSource
      A default DashChunkSource implementation.
      - + DefaultDashChunkSource.Factory   - + DefaultDashChunkSource.RepresentationHolder
      Holds information about a snapshot of a single Representation.
      - + DefaultDashChunkSource.RepresentationSegmentIterator - + DefaultDatabaseProvider
      A DatabaseProvider that provides instances obtained from a SQLiteOpenHelper.
      - + DefaultDataSource
      A DataSource that supports multiple URI schemes.
      - + DefaultDataSource.Factory - + DefaultDataSourceFactory Deprecated. - + DefaultDownloaderFactory
      Default DownloaderFactory, supporting creation of progressive, DASH, HLS and SmoothStreaming downloaders.
      - + DefaultDownloadIndex
      A DownloadIndex that uses SQLite to persist Downloads.
      - + DefaultDrmSessionManager
      A DrmSessionManager that supports playbacks using ExoMediaDrm.
      - + DefaultDrmSessionManager.Builder
      Builder for DefaultDrmSessionManager instances.
      - + DefaultDrmSessionManager.MissingSchemeDataException - + DefaultDrmSessionManager.Mode
      Determines the action to be done after a session acquired.
      - + DefaultDrmSessionManagerProvider
      Default implementation of DrmSessionManagerProvider.
      - + DefaultEncoderFactory
      A default implementation of Codec.EncoderFactory.
      - + DefaultEncoderFactory.Builder
      A builder for DefaultEncoderFactory instances.
      - + DefaultExtractorInput
      An ExtractorInput that wraps a DataReader.
      - + DefaultExtractorsFactory
      An ExtractorsFactory that provides an array of extractors for the following formats: @@ -2280,1851 +2292,1851 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); com.google.android.exoplayer2.ext.flac.FlacExtractor is used.
      - + DefaultHlsDataSourceFactory
      Default implementation of HlsDataSourceFactory.
      - + DefaultHlsExtractorFactory
      Default HlsExtractorFactory implementation.
      - + DefaultHlsPlaylistParserFactory
      Default implementation for HlsPlaylistParserFactory.
      - + DefaultHlsPlaylistTracker
      Default implementation for HlsPlaylistTracker.
      - + DefaultHttpDataSource
      An HttpDataSource that uses Android's HttpURLConnection.
      - + DefaultHttpDataSource.Factory - + DefaultLivePlaybackSpeedControl
      A LivePlaybackSpeedControl that adjusts the playback speed using a proportional controller.
      - + DefaultLivePlaybackSpeedControl.Builder - + DefaultLoadControl
      The default LoadControl implementation.
      - + DefaultLoadControl.Builder
      Builder for DefaultLoadControl.
      - + DefaultLoadErrorHandlingPolicy
      Default implementation of LoadErrorHandlingPolicy.
      - + DefaultMediaCodecAdapterFactory - + DefaultMediaDescriptionAdapter - + DefaultMediaItemConverter
      Default MediaItemConverter implementation.
      - + DefaultMediaItemConverter
      Default implementation of MediaItemConverter.
      - + DefaultMediaSourceFactory
      The default MediaSource.Factory implementation.
      - + DefaultMediaSourceFactory.AdsLoaderProvider Deprecated.
      Use AdsLoader.Provider instead.
      - + DefaultMuxer
      A default Muxer implementation.
      - + DefaultMuxer.Factory - + DefaultPlaybackSessionManager
      Default PlaybackSessionManager which instantiates a new session for each window in the timeline and also for each ad within the windows.
      - + DefaultRenderersFactory
      Default RenderersFactory implementation.
      - + DefaultRenderersFactory.ExtensionRendererMode
      Modes for using extension renderers.
      - + DefaultRenderersFactoryAsserts
      Assertions for DefaultRenderersFactory.
      - + DefaultRtpPayloadReaderFactory
      Default RtpPayloadReader.Factory implementation.
      - + DefaultSsChunkSource
      A default SsChunkSource implementation.
      - + DefaultSsChunkSource.Factory   - + DefaultTimeBar
      A time bar that shows a current position, buffered position, duration and ad markers.
      - + DefaultTrackNameProvider - + DefaultTrackSelector
      A default TrackSelector suitable for most use cases.
      - + DefaultTrackSelector.Parameters
      Extends DefaultTrackSelector.Parameters by adding fields that are specific to DefaultTrackSelector.
      - + DefaultTrackSelector.Parameters.Builder - + DefaultTrackSelector.ParametersBuilder Deprecated. - + DefaultTrackSelector.SelectionEligibility
      The extent to which tracks are eligible for selection.
      - + DefaultTrackSelector.SelectionOverride
      A track selection override.
      - + DefaultTsPayloadReaderFactory
      Default TsPayloadReader.Factory implementation.
      - + DefaultTsPayloadReaderFactory.Flags
      Flags controlling elementary stream readers' behavior.
      - + Descriptor
      A descriptor, as defined by ISO 23009-1, 2nd edition, 5.8.2.
      - + DeviceInfo
      Information about the playback device.
      - + DeviceInfo.PlaybackType
      Types of playback.
      - + DeviceMappedEncoderBitrateProvider
      Provides encoder bitrates that should target 0.95 SSIM or higher, accounting for device used.
      - + DolbyVisionConfig
      Dolby Vision configuration data.
      - + Download
      Represents state of a download.
      - + Download.FailureReason
      Failure reasons.
      - + Download.State
      Download states.
      - + DownloadBuilder
      Builder for Download.
      - + DownloadCursor
      Provides random read-write access to the result set returned by a database query.
      - + Downloader
      Downloads and removes a piece of content.
      - + Downloader.ProgressListener
      Receives progress updates during download operations.
      - + DownloaderFactory
      Creates Downloaders for given DownloadRequests.
      - + DownloadException
      Thrown on an error during downloading.
      - + DownloadHelper
      A helper for initializing and removing downloads.
      - + DownloadHelper.Callback
      A callback to be notified when the DownloadHelper is prepared.
      - + DownloadHelper.LiveContentUnsupportedException
      Thrown at an attempt to download live content.
      - + DownloadIndex
      An index of Downloads.
      - + DownloadManager
      Manages downloads.
      - + DownloadManager.Listener
      Listener for DownloadManager events.
      - + DownloadNotificationHelper
      Helper for creating download notifications.
      - + DownloadProgress
      Mutable Download progress.
      - + DownloadRequest
      Defines content to be downloaded.
      - + DownloadRequest.Builder
      A builder for download requests.
      - + DownloadRequest.UnsupportedRequestException
      Thrown when the encoded request data belongs to an unsupported request type.
      - + DownloadService
      A Service for downloading media.
      - + DrmInitData
      Initialization data for one or more DRM schemes.
      - + DrmInitData.SchemeData
      Scheme initialization data.
      - + DrmSession
      A DRM session.
      - + DrmSession.DrmSessionException
      Wraps the throwable which is the cause of the error state.
      - + DrmSession.State
      The state of the DRM session.
      - + DrmSessionEventListener
      Listener of DrmSessionManager events.
      - + DrmSessionEventListener.EventDispatcher
      Dispatches events to DrmSessionEventListeners.
      - + DrmSessionManager
      Manages a DRM session.
      - + DrmSessionManager.DrmSessionReference
      Represents a single reference count of a DrmSession, while deliberately not giving access to the underlying session.
      - + DrmSessionManagerProvider
      A provider to obtain a DrmSessionManager suitable for playing the content described by a MediaItem.
      - + DrmUtil
      DRM-related utility methods.
      - + DrmUtil.ErrorSource
      Identifies the operation which caused a DRM-related error.
      - + DtsReader
      Parses a continuous DTS byte stream and extracts individual samples.
      - + DtsUtil
      Utility methods for parsing DTS frames.
      - + DummyExoMediaDrm
      An ExoMediaDrm that does not support any protection schemes.
      - + DummyExtractorOutput
      A fake ExtractorOutput implementation.
      - + DummyMainThread
      Helper class to simulate main/UI thread in tests.
      - + DummyMainThread.TestRunnable
      Runnable variant which can throw a checked exception.
      - + DummyTrackOutput
      A fake TrackOutput implementation.
      - + DumpableFormat
      Wraps a Format to allow dumping it.
      - + Dumper
      Helper utility to dump field values.
      - + Dumper.Dumpable
      Provides custom dump method.
      - + DumpFileAsserts
      Helper class to enable assertions based on golden-data dump files.
      - + DvbDecoder
      A SimpleSubtitleDecoder for DVB subtitles.
      - + DvbSubtitleReader
      Parses DVB subtitle data and extracts individual frames.
      - + EbmlProcessor
      Defines EBML element IDs/types and processes events.
      - + EbmlProcessor.ElementType
      EBML element types.
      - + Effect
      Marker interface for a video frame effect.
      - + EGLSurfaceTexture
      Generates a SurfaceTexture using EGL/GLES functions.
      - + EGLSurfaceTexture.SecureMode
      Secure mode to be used by the EGL surface and context.
      - + EGLSurfaceTexture.TextureImageListener
      Listener to be called when the texture image on SurfaceTexture has been updated.
      - + ElementaryStreamReader
      Extracts individual samples from an elementary media stream, preserving original order.
      - + EmptySampleStream
      An empty SampleStream.
      - + EncoderBitrateProvider
      Provides bitrates for encoders to use as a target.
      - + EncoderSelector
      Selector of MediaCodec encoder instances.
      - + EncoderUtil
      Utility methods for MediaCodec encoders.
      - + ErrorMessageProvider<T extends Throwable>
      Converts throwables into error codes and user readable error messages.
      - + ErrorStateDrmSession
      A DrmSession that's in a terminal error state.
      - + EventLogger
      Logs events from Player and other core components using Log.
      - + EventMessage
      An Event Message (emsg) as defined in ISO 23009-1.
      - + EventMessageDecoder
      Decodes data encoded by EventMessageEncoder.
      - + EventMessageEncoder
      Encodes data that can be decoded by EventMessageDecoder.
      - + EventStream
      A DASH in-MPD EventStream element, as defined by ISO/IEC 23009-1, 2nd edition, section 5.10.
      - + ExoDatabaseProvider Deprecated. - + ExoHostedTest
      A HostActivity.HostedTest for ExoPlayer playback tests.
      - + ExoMediaDrm
      Used to obtain keys for decrypting protected media streams.
      - + ExoMediaDrm.AppManagedProvider
      Provides an ExoMediaDrm instance owned by the app.
      - + ExoMediaDrm.KeyRequest
      Contains data used to request keys from a license server.
      - + ExoMediaDrm.KeyRequest.RequestType
      Key request types.
      - + ExoMediaDrm.KeyStatus
      Defines the status of a key.
      - + ExoMediaDrm.OnEventListener
      Called when a DRM event occurs.
      - + ExoMediaDrm.OnExpirationUpdateListener
      Called when a session expiration update occurs.
      - + ExoMediaDrm.OnKeyStatusChangeListener
      Called when the keys in a DRM session change state.
      - + ExoMediaDrm.Provider
      Provider for ExoMediaDrm instances.
      - + ExoMediaDrm.ProvisionRequest
      Contains data to request a certificate from a provisioning server.
      - + ExoPlaybackException
      Thrown when a non locally recoverable playback failure occurs.
      - + ExoPlaybackException.Type
      The type of source that produced the error.
      - + ExoPlayer
      An extensible media player that plays MediaSources.
      - + ExoPlayer.AudioComponent Deprecated.
      Use ExoPlayer, as the ExoPlayer.AudioComponent methods are defined by that interface.
      - + ExoPlayer.AudioOffloadListener
      A listener for audio offload events.
      - + ExoPlayer.Builder
      A builder for ExoPlayer instances.
      - + ExoPlayer.DeviceComponent Deprecated.
      Use Player, as the ExoPlayer.DeviceComponent methods are defined by that interface.
      - + ExoPlayer.TextComponent Deprecated.
      Use Player, as the ExoPlayer.TextComponent methods are defined by that interface.
      - + ExoPlayer.VideoComponent Deprecated.
      Use ExoPlayer, as the ExoPlayer.VideoComponent methods are defined by that interface.
      - + ExoplayerCuesDecoder
      A SubtitleDecoder that decodes subtitle samples of type MimeTypes.TEXT_EXOPLAYER_CUES
      - + ExoPlayerLibraryInfo
      Information about the media libraries.
      - + ExoPlayerTestRunner
      Helper class to run an ExoPlayer test.
      - + ExoPlayerTestRunner.Builder
      Builder to set-up an ExoPlayerTestRunner.
      - + ExoTimeoutException
      A timeout of an operation on the ExoPlayer playback thread.
      - + ExoTimeoutException.TimeoutOperation
      The operation which produced the timeout error.
      - + ExoTrackSelection - + ExoTrackSelection.Definition
      Contains of a subset of selected tracks belonging to a TrackGroup.
      - + ExoTrackSelection.Factory
      Factory for ExoTrackSelection instances.
      - + Extractor
      Extracts media data from a container format.
      - + Extractor.ReadResult
      Result values that can be returned by Extractor.read(ExtractorInput, PositionHolder).
      - + ExtractorAsserts
      Assertion methods for Extractor.
      - + ExtractorAsserts.AssertionConfig
      A config for the assertions made (e.g.
      - + ExtractorAsserts.AssertionConfig.Builder
      Builder for ExtractorAsserts.AssertionConfig instances.
      - + ExtractorAsserts.ExtractorFactory
      A factory for Extractor instances.
      - + ExtractorAsserts.SimulationConfig
      A config of different environments to simulate and extractor behaviours to test.
      - + ExtractorInput
      Provides data to be consumed by an Extractor.
      - + ExtractorOutput
      Receives stream level data extracted by an Extractor.
      - + ExtractorsFactory
      Factory for arrays of Extractor instances.
      - + ExtractorUtil
      Extractor related utility methods.
      - + FailOnCloseDataSink
      A DataSink that can simulate caching the bytes being written to it, and then failing to persist them when FailOnCloseDataSink.close() is called.
      - + FailOnCloseDataSink.Factory
      Factory to create a FailOnCloseDataSink.
      - + FakeAdaptiveDataSet
      Fake data set emulating the data of an adaptive media source.
      - + FakeAdaptiveDataSet.Factory
      Factory for FakeAdaptiveDataSets.
      - + FakeAdaptiveDataSet.Iterator
      MediaChunkIterator for the chunks defined by a fake adaptive data set.
      - + FakeAdaptiveMediaPeriod
      Fake MediaPeriod that provides tracks from the given TrackGroupArray.
      - + FakeAdaptiveMediaSource
      Fake MediaSource that provides a given timeline.
      - + FakeAudioRenderer - + FakeChunkSource
      Fake ChunkSource with adaptive media chunks of a given duration.
      - + FakeChunkSource.Factory
      Factory for a FakeChunkSource.
      - + FakeClock
      Fake Clock implementation that allows to advance the time manually to trigger pending timed messages.
      - + FakeCryptoConfig - + FakeDataSet
      Collection of FakeDataSet.FakeData to be served by a FakeDataSource.
      - + FakeDataSet.FakeData
      Container of fake data to be served by a FakeDataSource.
      - + FakeDataSet.FakeData.Segment
      A segment of FakeDataSet.FakeData.
      - + FakeDataSource
      A fake DataSource capable of simulating various scenarios.
      - + FakeDataSource.Factory
      Factory to create a FakeDataSource.
      - + FakeExoMediaDrm
      A fake implementation of ExoMediaDrm for use in tests.
      - + FakeExoMediaDrm.Builder
      Builder for FakeExoMediaDrm instances.
      - + FakeExoMediaDrm.LicenseServer
      An license server implementation to interact with FakeExoMediaDrm.
      - + FakeExtractorInput
      A fake ExtractorInput capable of simulating various scenarios.
      - + FakeExtractorInput.Builder
      Builder of FakeExtractorInput instances.
      - + FakeExtractorInput.SimulatedIOException
      Thrown when simulating an IOException.
      - + FakeExtractorOutput - + FakeMediaChunk - + FakeMediaChunkIterator - + FakeMediaClockRenderer
      Fake abstract Renderer which is also a MediaClock.
      - + FakeMediaPeriod
      Fake MediaPeriod that provides tracks from the given TrackGroupArray.
      - + FakeMediaPeriod.TrackDataFactory
      A factory to create the test data for a particular track.
      - + FakeMediaSource
      Fake MediaSource that provides a given timeline.
      - + FakeMediaSource.InitialTimeline
      A forwarding timeline to provide an initial timeline for fake multi window sources.
      - + FakeMediaSourceFactory
      Fake MediaSourceFactory that creates a FakeMediaSource.
      - + FakeMetadataEntry - + FakeRenderer
      Fake Renderer that supports any format with the matching track type.
      - + FakeSampleStream
      Fake SampleStream that outputs a given Format and any amount of items.
      - + FakeSampleStream.FakeSampleStreamItem - + FakeShuffleOrder
      Fake ShuffleOrder which returns a reverse order.
      - + FakeTimeline
      Fake Timeline which can be setup to return custom FakeTimeline.TimelineWindowDefinitions.
      - + FakeTimeline.TimelineWindowDefinition
      Definition used to define a FakeTimeline.
      - + FakeTrackOutput
      A fake TrackOutput.
      - + FakeTrackOutput.Factory
      Factory for FakeTrackOutput instances.
      - + FakeTrackSelection
      A fake ExoTrackSelection that only returns 1 fixed track, and allows querying the number of calls to its methods.
      - + FakeTrackSelector - + FakeVideoRenderer - + FfmpegAudioRenderer
      Decodes and renders audio using FFmpeg.
      - + FfmpegDecoderException
      Thrown when an FFmpeg decoder error occurs.
      - + FfmpegLibrary
      Configures and queries the underlying native library.
      - + FileDataSource
      A DataSource for reading local files.
      - + FileDataSource.Factory - + FileDataSource.FileDataSourceException
      Thrown when a FileDataSource encounters an error reading a file.
      - + FileTypes
      Defines common file type constants and helper methods.
      - + FileTypes.Type
      File types.
      - + FilterableManifest<T>
      A manifest that can generate copies of itself including only the streams specified by the given keys.
      - + FilteringHlsPlaylistParserFactory
      A HlsPlaylistParserFactory that includes only the streams identified by the given stream keys.
      - + FilteringManifestParser<T extends FilterableManifest<T>>
      A manifest parser that includes only the streams identified by the given stream keys.
      - + FixedTrackSelection
      A TrackSelection consisting of a single track.
      - + FlacConstants
      Defines constants used by the FLAC extractor.
      - + FlacDecoder
      Flac decoder.
      - + FlacDecoderException
      Thrown when an Flac decoder error occurs.
      - + FlacExtractor
      Facilitates the extraction of data from the FLAC container format.
      - + FlacExtractor
      Extracts data from FLAC container format.
      - + FlacExtractor.Flags
      Flags controlling the behavior of the extractor.
      - + FlacExtractor.Flags
      Flags controlling the behavior of the extractor.
      - + FlacFrameReader
      Reads and peeks FLAC frame elements according to the FLAC format specification.
      - + FlacFrameReader.SampleNumberHolder
      Holds a sample number.
      - + FlacLibrary
      Configures and queries the underlying native library.
      - + FlacMetadataReader
      Reads and peeks FLAC stream metadata elements according to the FLAC format specification.
      - + FlacMetadataReader.FlacStreamMetadataHolder - + FlacSeekTableSeekMap
      A SeekMap implementation for FLAC streams that contain a seek table.
      - + FlacStreamMetadata
      Holder for FLAC metadata.
      - + FlacStreamMetadata.SeekTable
      A FLAC seek table.
      - + FlagSet
      A set of integer flags.
      - + FlagSet.Builder
      A builder for FlagSet instances.
      - + FlvExtractor
      Extracts data from the FLV container format.
      - + Format
      Represents a media format.
      - + Format.Builder
      Builds Format instances.
      - + FormatHolder
      Holds a Format.
      - + ForwardingAudioSink
      An overridable AudioSink implementation forwarding all methods to another sink.
      - + ForwardingExtractorInput
      An overridable ExtractorInput implementation forwarding all methods to another input.
      - + ForwardingPlayer
      A Player that forwards operations to another Player.
      - + ForwardingTimeline
      An overridable Timeline implementation forwarding all methods to another timeline.
      - + FragmentedMp4Extractor
      Extracts data from the FMP4 container format.
      - + FragmentedMp4Extractor.Flags
      Flags controlling the behavior of the extractor.
      - + FrameInfo
      Value class specifying information about a decoded video frame.
      - + FrameProcessingException
      Thrown when an exception occurs while applying effects to video frames.
      - + FrameProcessor
      Interface for a frame processor that applies changes to individual video frames.
      - + FrameProcessor.Factory
      A factory for FrameProcessor instances.
      - + FrameProcessor.Listener
      Listener for asynchronous frame processing events.
      - + FrameworkCryptoConfig - + FrameworkMediaDrm
      An ExoMediaDrm implementation that wraps the framework MediaDrm.
      - + GaplessInfoHolder
      Holder for gapless playback information.
      - + Gav1Decoder
      Gav1 decoder.
      - + Gav1DecoderException
      Thrown when a libgav1 decoder error occurs.
      - + Gav1Library
      Configures and queries the underlying native library.
      - + GeobFrame
      GEOB (General Encapsulated Object) ID3 frame.
      - + GlEffect
      Interface for a video frame effect with a GlTextureProcessor implementation.
      - + GlEffectsFrameProcessor
      A FrameProcessor implementation that applies GlEffect instances using OpenGL on a background thread.
      - + GlEffectsFrameProcessor.Factory
      A factory for GlEffectsFrameProcessor instances.
      - + GlMatrixTransformation
      Specifies a 4x4 transformation Matrix to apply in the vertex shader for each frame.
      - + GlProgram
      Represents a GLSL shader program.
      - + GlTextureProcessor
      Processes frames from one OpenGL 2D texture to another.
      - + GlTextureProcessor.ErrorListener
      Listener for frame processing errors.
      - + GlTextureProcessor.InputListener
      Listener for input-related frame processing events.
      - + GlTextureProcessor.OutputListener
      Listener for output-related frame processing events.
      - + GlUtil
      OpenGL ES utilities.
      - + GlUtil.GlException
      Thrown when an OpenGL error occurs.
      - + H262Reader
      Parses a continuous H262 byte stream and extracts individual frames.
      - + H263Reader
      Parses an ISO/IEC 14496-2 (MPEG-4 Part 2) or ITU-T Recommendation H.263 byte stream and extracts individual frames.
      - + H264Reader
      Parses a continuous H264 byte stream and extracts individual frames.
      - + H265Reader
      Parses a continuous H.265 byte stream and extracts individual frames.
      - + HandlerWrapper
      An interface to call through to a Handler.
      - + HandlerWrapper.Message
      A message obtained from the handler.
      - + HeartRating
      A rating expressed as "heart" or "no heart".
      - + HevcConfig
      HEVC configuration data.
      - + HlsDataSourceFactory
      Creates DataSources for HLS playlists, encryption and media chunks.
      - + HlsDownloader
      A downloader for HLS streams.
      - + HlsExtractorFactory
      Factory for HLS media chunk extractors.
      - + HlsManifest
      Holds a multivariant playlist along with a snapshot of one of its media playlists.
      - + HlsMasterPlaylist Deprecated. - + HlsMediaChunkExtractor
      Extracts samples and track Formats from HlsMediaChunks.
      - + HlsMediaPeriod
      A MediaPeriod that loads an HLS stream.
      - + HlsMediaPlaylist
      Represents an HLS media playlist.
      - + HlsMediaPlaylist.Part
      A media part.
      - + HlsMediaPlaylist.PlaylistType
      Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE.
      - + HlsMediaPlaylist.RenditionReport
      A rendition report for an alternative rendition defined in another media playlist.
      - + HlsMediaPlaylist.Segment
      Media segment reference.
      - + HlsMediaPlaylist.SegmentBase
      The base for a HlsMediaPlaylist.Segment or a HlsMediaPlaylist.Part required for playback.
      - + HlsMediaPlaylist.ServerControl
      Server control attributes.
      - + HlsMediaSource
      An HLS MediaSource.
      - + HlsMediaSource.Factory
      Factory for HlsMediaSources.
      - + HlsMediaSource.MetadataType
      The types of metadata that can be extracted from HLS streams.
      - + HlsMultivariantPlaylist
      Represents an HLS multivariant playlist.
      - + HlsMultivariantPlaylist.Rendition
      A rendition (i.e.
      - + HlsMultivariantPlaylist.Variant
      A variant (i.e.
      - + HlsPlaylist
      Represents an HLS playlist.
      - + HlsPlaylistParser
      HLS playlists parsing logic.
      - + HlsPlaylistParser.DeltaUpdateException
      Exception thrown when merging a delta update fails.
      - + HlsPlaylistParserFactory
      Factory for HlsPlaylist parsers.
      - + HlsPlaylistTracker
      Tracks playlists associated to an HLS stream and provides snapshots.
      - + HlsPlaylistTracker.Factory
      Factory for HlsPlaylistTracker instances.
      - + HlsPlaylistTracker.PlaylistEventListener
      Called on playlist loading events.
      - + HlsPlaylistTracker.PlaylistResetException
      Thrown when the media sequence of a new snapshot indicates the server has reset.
      - + HlsPlaylistTracker.PlaylistStuckException
      Thrown when a playlist is considered to be stuck due to a server side error.
      - + HlsPlaylistTracker.PrimaryPlaylistListener
      Listener for primary playlist changes.
      - + HlsTrackMetadataEntry
      Holds metadata associated to an HLS media track.
      - + HlsTrackMetadataEntry.VariantInfo
      Holds attributes defined in an EXT-X-STREAM-INF tag.
      - + HorizontalTextInVerticalContextSpan
      A styling span for horizontal text in a vertical context.
      - + HostActivity
      A host activity for performing playback tests.
      - + HostActivity.HostedTest
      Interface for tests that run inside of a HostActivity.
      - + HslAdjustment
      Adjusts the HSL (Hue, Saturation, and Lightness) of a frame.
      - + HslAdjustment.Builder
      A builder for HslAdjustment instances.
      - + HttpDataSource
      An HTTP DataSource.
      - + HttpDataSource.BaseFactory
      Base implementation of HttpDataSource.Factory that sets default request properties.
      - + HttpDataSource.CleartextNotPermittedException
      Thrown when cleartext HTTP traffic is not permitted.
      - + HttpDataSource.Factory
      A factory for HttpDataSource instances.
      - + HttpDataSource.HttpDataSourceException
      Thrown when an error is encountered when trying to read from a HttpDataSource.
      - + HttpDataSource.HttpDataSourceException.Type
      The type of operation that produced the error.
      - + HttpDataSource.InvalidContentTypeException
      Thrown when the content type is invalid.
      - + HttpDataSource.InvalidResponseCodeException
      Thrown when an attempt to open a connection results in a response code not in the 2xx range.
      - + HttpDataSource.RequestProperties
      Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers in @@ -4132,379 +4144,379 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); state.
      - + HttpDataSourceTestEnv
      A JUnit Rule that creates test resources for HttpDataSource contract tests.
      - + HttpMediaDrmCallback
      A MediaDrmCallback that makes requests using DataSource instances.
      - + HttpUtil
      Utility methods for HTTP.
      - + IcyDecoder
      Decodes ICY stream information.
      - + IcyHeaders
      ICY headers.
      - + IcyInfo
      ICY in-stream information.
      - + Id3Decoder
      Decodes ID3 tags.
      - + Id3Decoder.FramePredicate
      A predicate for determining whether individual frames should be decoded.
      - + Id3Frame
      Base class for ID3 frames.
      - + Id3Peeker
      Peeks data from the beginning of an ExtractorInput to determine if there is any ID3 tag.
      - + Id3Reader
      Parses ID3 data and extracts individual text information frames.
      - + IllegalSeekPositionException
      Thrown when an attempt is made to seek to a position that does not exist in the player's Timeline.
      - + ImaAdsLoader
      AdsLoader using the IMA SDK.
      - + ImaAdsLoader.Builder
      Builder for ImaAdsLoader.
      - + ImaServerSideAdInsertionMediaSource
      MediaSource for IMA server side inserted ad streams.
      - + ImaServerSideAdInsertionMediaSource.AdsLoader
      An ads loader for IMA server side ad insertion streams.
      - + ImaServerSideAdInsertionMediaSource.AdsLoader.Builder - + ImaServerSideAdInsertionMediaSource.AdsLoader.State
      The state of the ImaServerSideAdInsertionMediaSource.AdsLoader that can be used when resuming from the background.
      - + ImaServerSideAdInsertionMediaSource.Factory - + ImaServerSideAdInsertionUriBuilder
      Builder for URI for IMA DAI streams.
      - + IndexSeekMap
      A SeekMap implementation based on a mapping between times and positions in the input stream.
      - + InitializationChunk
      A Chunk that uses an Extractor to decode initialization data for single track.
      - + InputReaderAdapterV30
      MediaParser.SeekableInputReader implementation wrapping a DataReader.
      - + InternalFrame
      Internal ID3 frame that is intended for use by the player.
      - + JpegExtractor
      Extracts JPEG image using the Exif format.
      - + KeysExpiredException
      Thrown when the drm keys loaded into an open session expire.
      - + LanguageFeatureSpan
      Marker interface for span classes that carry language features rather than style information.
      - + LatmReader
      Parses and extracts samples from an AAC/LATM elementary stream.
      - + LeanbackPlayerAdapter
      Leanback PlayerAdapter implementation for Player.
      - + LeastRecentlyUsedCacheEvictor
      Evicts least recently used cache files first.
      - + LegacyMediaPlayerWrapper
      A Player wrapper for the legacy Android platform MediaPlayer.
      - + LibflacAudioRenderer
      Decodes and renders audio using the native Flac decoder.
      - + Libgav1VideoRenderer
      Decodes and renders video using libgav1 decoder.
      - + LibopusAudioRenderer
      Decodes and renders audio using the native Opus decoder.
      - + LibraryLoader
      Configurable loader for native libraries.
      - + LibvpxVideoRenderer
      Decodes and renders video using the native VP9 decoder.
      - + ListenerSet<T extends @NonNull Object>
      A set of listeners.
      - + ListenerSet.Event<T>
      An event sent to a listener.
      - + ListenerSet.IterationFinishedEvent<T>
      An event sent to a listener when all other events sent during one Looper message queue iteration were handled by the listener.
      - + LivePlaybackSpeedControl
      Controls the playback speed while playing live content in order to maintain a steady target live offset.
      - + LoadControl
      Controls buffering of media.
      - + Loader
      Manages the background loading of Loader.Loadables.
      - + Loader.Callback<T extends Loader.Loadable>
      A callback to be notified of Loader events.
      - + Loader.Loadable
      An object that can be loaded using a Loader.
      - + Loader.LoadErrorAction - + Loader.ReleaseCallback
      A callback to be notified when a Loader has finished being released.
      - + Loader.UnexpectedLoaderException
      Thrown when an unexpected exception or error is encountered during loading.
      - + LoaderErrorThrower
      Conditionally throws errors affecting a Loader.
      - + LoaderErrorThrower.Dummy
      A LoaderErrorThrower that never throws.
      - + LoadErrorHandlingPolicy
      A policy that defines how load errors are handled.
      - + LoadErrorHandlingPolicy.FallbackOptions
      Holds information about the available fallback options.
      - + LoadErrorHandlingPolicy.FallbackSelection
      A selected fallback option.
      - + LoadErrorHandlingPolicy.FallbackType
      Fallback type.
      - + LoadErrorHandlingPolicy.LoadErrorInfo
      Holds information about a load task error.
      - + LoadEventInfo
      MediaSource load event information.
      - + LocalMediaDrmCallback
      A MediaDrmCallback that provides a fixed response to key requests.
      - + Log
      Wrapper around Log which allows to set the log level and to specify a custom log output.
      - + Log.Logger
      Interface for a logger that can output messages with a tag.
      - + Log.LogLevel
      Log level for ExoPlayer logcat logging.
      - + LongArray
      An append-only, auto-growing long[].
      - + LoopingMediaSource Deprecated.
      To loop a MediaSource indefinitely, use Player.setRepeatMode(int) instead of this class.
      - + MappingTrackSelector
      Base class for TrackSelectors that first establish a mapping between TrackGroups @@ -4512,1184 +4524,1191 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); renderer.
      - + MappingTrackSelector.MappedTrackInfo
      Provides mapped track information for each renderer.
      - + MappingTrackSelector.MappedTrackInfo.RendererSupport
      Levels of renderer support.
      - + MaskingMediaPeriod
      Media period that defers calling MediaSource.createPeriod(MediaPeriodId, Allocator, long) on a given source until MaskingMediaPeriod.createPeriod(MediaPeriodId) has been called.
      - + MaskingMediaPeriod.PrepareListener
      Listener for preparation events.
      - + MaskingMediaSource
      A MediaSource that masks the Timeline with a placeholder until the actual media structure is known.
      - + MaskingMediaSource.PlaceholderTimeline
      A timeline with one dynamic window with a period of indeterminate duration.
      - + MatrixTransformation
      Specifies a 3x3 transformation Matrix to apply in the vertex shader for each frame.
      - + MatroskaExtractor
      Extracts data from the Matroska and WebM container formats.
      - + MatroskaExtractor.Flags
      Flags controlling the behavior of the extractor.
      - + MatroskaExtractor.Track
      Holds data corresponding to a single track.
      - + MdtaMetadataEntry
      Stores extensible metadata with handler type 'mdta'.
      - + MediaChunk
      An abstract base class for Chunks that contain media samples.
      - + MediaChunkIterator
      Iterator for media chunk sequences.
      - + MediaClock
      Tracks the progression of media time.
      - + MediaCodecAdapter
      Abstracts MediaCodec operations.
      - + MediaCodecAdapter.Configuration
      Configuration parameters for a MediaCodecAdapter.
      - + MediaCodecAdapter.Factory
      A factory for MediaCodecAdapter instances.
      - + MediaCodecAdapter.OnFrameRenderedListener
      Listener to be called when an output frame has rendered on the output surface.
      - + MediaCodecAudioRenderer
      Decodes and renders audio using MediaCodec and an AudioSink.
      - + MediaCodecDecoderException
      Thrown when a failure occurs in a MediaCodec decoder.
      - + MediaCodecInfo
      Information about a MediaCodec for a given mime type.
      - + MediaCodecRenderer
      An abstract renderer that uses MediaCodec to decode samples for rendering.
      - + MediaCodecRenderer.DecoderInitializationException
      Thrown when a failure occurs instantiating a decoder.
      - + MediaCodecSelector
      Selector of MediaCodec instances.
      - + MediaCodecUtil
      A utility class for querying the available codecs.
      - + MediaCodecUtil.DecoderQueryException
      Thrown when an error occurs querying the device for its underlying media capabilities.
      - + MediaCodecVideoDecoderException
      Thrown when a failure occurs in a MediaCodec video decoder.
      - + MediaCodecVideoRenderer
      Decodes and renders video using MediaCodec.
      - + MediaCodecVideoRenderer.CodecMaxValues   - + MediaDrmCallback
      Performs ExoMediaDrm key and provisioning requests.
      - + MediaDrmCallbackException
      Thrown when an error occurs while executing a DRM key or provisioning request.
      - + MediaFormatUtil
      Helper class containing utility methods for managing MediaFormat instances.
      - + MediaItem
      Representation of a media item.
      - + MediaItem.AdsConfiguration
      Configuration for playing back linear ads with a media item.
      - + MediaItem.AdsConfiguration.Builder
      Builder for MediaItem.AdsConfiguration instances.
      - + MediaItem.Builder
      A builder for MediaItem instances.
      - + MediaItem.ClippingConfiguration
      Optionally clips the media item to a custom start and end position.
      - + MediaItem.ClippingConfiguration.Builder
      Builder for MediaItem.ClippingConfiguration instances.
      - + MediaItem.ClippingProperties Deprecated. - + MediaItem.DrmConfiguration
      DRM configuration for a media item.
      - + MediaItem.DrmConfiguration.Builder - + MediaItem.LiveConfiguration
      Live playback configuration.
      - + MediaItem.LiveConfiguration.Builder
      Builder for MediaItem.LiveConfiguration instances.
      - + MediaItem.LocalConfiguration
      Properties for local playback.
      - + MediaItem.PlaybackProperties Deprecated. - + MediaItem.RequestMetadata
      Metadata that helps the player to understand a playback request represented by a MediaItem.
      - + MediaItem.RequestMetadata.Builder
      Builder for MediaItem.RequestMetadata instances.
      - + MediaItem.Subtitle Deprecated. - + MediaItem.SubtitleConfiguration
      Properties for a text track.
      - + MediaItem.SubtitleConfiguration.Builder
      Builder for MediaItem.SubtitleConfiguration instances.
      - + MediaItemConverter
      Converts between MediaItem and the Cast SDK's MediaQueueItem.
      - + MediaItemConverter
      Converts between Media2 MediaItem and ExoPlayer MediaItem.
      - + MediaLoadData
      Descriptor for data being loaded or selected by a MediaSource.
      - + MediaMetadata
      Metadata of a MediaItem, playlist, or a combination of multiple sources of Metadata.
      - + MediaMetadata.Builder
      A builder for MediaMetadata instances.
      - + MediaMetadata.FolderType
      The folder type of the media item.
      - + +MediaMetadata.MediaType + +
      The type of content described by the media item.
      + + + MediaMetadata.PictureType
      The picture type of the artwork.
      - + MediaMetricsListener
      An AnalyticsListener that interacts with the Android MediaMetricsManager.
      - + MediaParserChunkExtractor
      ChunkExtractor implemented on top of the platform's MediaParser.
      - + MediaParserExtractorAdapter
      ProgressiveMediaExtractor implemented on top of the platform's MediaParser.
      - + MediaParserHlsMediaChunkExtractor
      HlsMediaChunkExtractor implemented on top of the platform's MediaParser.
      - + MediaParserUtil
      Miscellaneous constants and utility methods related to the MediaParser integration.
      - + MediaPeriod
      Loads media corresponding to a Timeline.Period, and allows that media to be read.
      - + MediaPeriod.Callback
      A callback to be notified of MediaPeriod events.
      - + MediaPeriodAsserts
      Assertion methods for MediaPeriod.
      - + MediaPeriodAsserts.FilterableManifestMediaPeriodFactory<T extends FilterableManifest<T>>
      Interface to create media periods for testing based on a FilterableManifest.
      - + MediaPeriodId
      Identifies a specific playback of a Timeline.Period.
      - + MediaSessionConnector
      Connects a MediaSessionCompat to a Player.
      - + MediaSessionConnector.CaptionCallback
      Handles requests for enabling or disabling captions.
      - + MediaSessionConnector.CommandReceiver
      Receiver of media commands sent by a media controller.
      - + MediaSessionConnector.CustomActionProvider
      Provides a PlaybackStateCompat.CustomAction to be published and handles the action when sent by a media controller.
      - + MediaSessionConnector.DefaultMediaMetadataProvider
      Provides a default MediaMetadataCompat with properties and extras taken from the MediaDescriptionCompat of the MediaSessionCompat.QueueItem of the active queue item.
      - + MediaSessionConnector.MediaButtonEventHandler
      Handles a media button event.
      - + MediaSessionConnector.MediaMetadataProvider
      Provides a MediaMetadataCompat for a given player state.
      - + MediaSessionConnector.PlaybackActions
      Playback actions supported by the connector.
      - + MediaSessionConnector.PlaybackPreparer
      Interface to which playback preparation and play actions are delegated.
      - + MediaSessionConnector.QueueEditor
      Handles media session queue edits.
      - + MediaSessionConnector.QueueNavigator
      Handles queue navigation actions, and updates the media session queue by calling MediaSessionCompat.setQueue().
      - + MediaSessionConnector.RatingCallback
      Callback receiving a user rating for the active media item.
      - + MediaSource
      Defines and provides media to be played by an ExoPlayer.
      - + MediaSource.Factory
      Factory for creating MediaSources from MediaItems.
      - + MediaSource.MediaPeriodId
      Identifier for a MediaPeriod.
      - + MediaSource.MediaSourceCaller
      A caller of media sources, which will be notified of source events.
      - + MediaSourceEventListener
      Interface for callbacks to be notified of MediaSource events.
      - + MediaSourceEventListener.EventDispatcher
      Dispatches events to MediaSourceEventListeners.
      - + MediaSourceFactory Deprecated. - + MediaSourceTestRunner
      A runner for MediaSource tests.
      - + MergingMediaSource
      Merges multiple MediaSources.
      - + MergingMediaSource.IllegalMergeException
      Thrown when a MergingMediaSource cannot merge its sources.
      - + MergingMediaSource.IllegalMergeException.Reason
      The reason the merge failed.
      - + Metadata
      A collection of metadata entries.
      - + Metadata.Entry
      A metadata entry.
      - + MetadataDecoder
      Decodes metadata from binary data.
      - + MetadataDecoderFactory
      A factory for MetadataDecoder instances.
      - + MetadataInputBuffer - + MetadataOutput
      Receives metadata output.
      - + MetadataRenderer
      A renderer for metadata.
      - + MetadataRetriever
      Retrieves the static metadata of MediaItems.
      - + MimeTypes
      Defines common MIME types and helper methods.
      - + MlltFrame
      MPEG location lookup table frame.
      - + MotionPhotoMetadata
      Metadata of a motion photo file.
      - + Mp3Extractor
      Extracts data from the MP3 container format.
      - + Mp3Extractor.Flags
      Flags controlling the behavior of the extractor.
      - + Mp4Extractor
      Extracts data from the MP4 container format.
      - + Mp4Extractor.Flags
      Flags controlling the behavior of the extractor.
      - + Mp4WebvttDecoder
      A SimpleSubtitleDecoder for Webvtt embedded in a Mp4 container file.
      - + MpegAudioReader
      Parses a continuous MPEG Audio byte stream and extracts individual frames.
      - + MpegAudioUtil
      Utility methods for handling MPEG audio streams.
      - + MpegAudioUtil.Header
      Stores the metadata for an MPEG audio frame.
      - + Muxer
      Abstracts media muxing operations.
      - + Muxer.Factory
      Factory for muxers.
      - + Muxer.MuxerException
      Thrown when a muxing failure occurs.
      - + NalUnitUtil
      Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
      - + NalUnitUtil.H265SpsData
      Holds data parsed from a H.265 sequence parameter set NAL unit.
      - + NalUnitUtil.PpsData
      Holds data parsed from a picture parameter set NAL unit.
      - + NalUnitUtil.SpsData
      Holds data parsed from a H.264 sequence parameter set NAL unit.
      - + NetworkTypeObserver
      Observer for network type changes.
      - + NetworkTypeObserver.Listener
      A listener for network type changes.
      - + NonNullApi
      Annotation to declare all type usages in the annotated instance as Nonnull, unless explicitly marked with a nullable annotation.
      - + NoOpCacheEvictor
      Evictor that doesn't ever evict cache files.
      - + NoSampleRenderer
      A Renderer implementation whose track type is C.TRACK_TYPE_NONE and does not consume data from its SampleStream.
      - + NotificationUtil
      Utility methods for displaying Notifications.
      - + NotificationUtil.Importance
      Notification channel importance levels.
      - + OfflineLicenseHelper
      Helper class to download, renew and release offline licenses.
      - + OggExtractor
      Extracts data from the Ogg container format.
      - + OkHttpDataSource
      An HttpDataSource that delegates to Square's Call.Factory.
      - + OkHttpDataSource.Factory - + OkHttpDataSourceFactory Deprecated. - + OpusDecoder
      Opus decoder.
      - + OpusDecoderException
      Thrown when an Opus decoder error occurs.
      - + OpusLibrary
      Configures and queries the underlying native library.
      - + OpusUtil
      Utility methods for handling Opus audio streams.
      - + OutputConsumerAdapterV30
      MediaParser.OutputConsumer implementation that redirects output to an ExtractorOutput.
      - + ParsableBitArray
      Wraps a byte array, providing methods that allow it to be read as a bitstream.
      - + ParsableByteArray
      Wraps a byte array, providing a set of methods for parsing data from it.
      - + ParsableNalUnitBitArray
      Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream.
      - + ParserException
      Thrown when an error occurs parsing media data and metadata.
      - + ParsingLoadable<T>
      A Loader.Loadable for objects that can be parsed from binary data using a ParsingLoadable.Parser.
      - + ParsingLoadable.Parser<T>
      Parses an object from loaded data.
      - + PassthroughSectionPayloadReader
      A SectionPayloadReader that directly outputs the section bytes as sample data.
      - + PercentageRating
      A rating expressed as a percentage.
      - + Period
      Encapsulates media content components over a contiguous period of time.
      - + PesReader
      Parses PES packet data and extracts samples.
      - + PgsDecoder
      A SimpleSubtitleDecoder for PGS subtitles.
      - + PictureFrame
      A picture parsed from a Vorbis Comment or a FLAC picture block.
      - + PlaceholderDataSource
      A DataSource which provides no data.
      - + PlaceholderSurface
      A placeholder Surface.
      - + PlatformScheduler
      A Scheduler that uses JobScheduler.
      - + PlatformScheduler.PlatformSchedulerService
      A JobService that starts the target service if the requirements are met.
      - + PlaybackException
      Thrown when a non locally recoverable playback failure occurs.
      - + PlaybackException.ErrorCode
      Codes that identify causes of player errors.
      - + PlaybackOutput
      Class to capture output from a playback test.
      - + PlaybackParameters
      Parameters that apply to playback, including speed setting.
      - + PlaybackSessionManager
      Manager for active playback sessions.
      - + PlaybackSessionManager.Listener
      A listener for session updates.
      - + PlaybackStats
      Statistics about playbacks.
      - + PlaybackStats.EventTimeAndException
      Stores an exception with the event time at which it occurred.
      - + PlaybackStats.EventTimeAndFormat
      Stores a format with the event time at which it started being used, or null to indicate that no format was used.
      - + PlaybackStats.EventTimeAndPlaybackState
      Stores a playback state with the event time at which it became active.
      - + PlaybackStatsListener
      AnalyticsListener to gather PlaybackStats from the player.
      - + PlaybackStatsListener.Callback
      A listener for PlaybackStats updates.
      - + Player
      A media player interface defining traditional high-level functionality, such as the ability to play, pause, seek and query properties of the currently playing media.
      - + Player.Command -
      Commands that can be executed on a Player.
      +
      Commands that indicate which method calls are currently permitted on a particular + Player instance.
      - + Player.Commands -
      A set of commands.
      +
      A set of commands.
      - + Player.Commands.Builder
      A builder for Player.Commands instances.
      - + Player.DiscontinuityReason
      Reasons for position discontinuities.
      - + Player.Event
      Events that can be reported via Player.Listener.onEvents(Player, Events).
      - + Player.Events -
      A set of events.
      +
      A set of events.
      - + Player.Listener -
      Listener of all changes in the Player.
      +
      Listener for changes in a Player.
      - + Player.MediaItemTransitionReason
      Reasons for media item transitions.
      - + Player.PlaybackSuppressionReason
      Reason why playback is suppressed even though Player.getPlayWhenReady() is true.
      - + Player.PlayWhenReadyChangeReason
      Reasons for playWhenReady changes.
      - + Player.PositionInfo
      Position info describing a playback position involved in a discontinuity.
      - + Player.RepeatMode
      Repeat modes for playback.
      - + Player.State
      Playback state.
      - + Player.TimelineChangeReason
      Reasons for timeline changes.
      - + PlayerControlView
      A view for controlling Player instances.
      - + PlayerControlView.ProgressUpdateListener
      Listener to be notified when progress has been updated.
      - + PlayerControlView.VisibilityListener
      Listener to be notified about changes of the visibility of the UI control.
      - + PlayerEmsgHandler
      Handles all emsg messages from all media tracks for the player.
      - + PlayerEmsgHandler.PlayerEmsgCallback
      Callbacks for player emsg events encountered during DASH live stream.
      - + PlayerId
      Identifier for a player instance.
      - + PlayerMessage
      Defines a player message which can be sent with a PlayerMessage.Sender and received by a PlayerMessage.Target.
      - + PlayerMessage.Sender
      A sender for messages.
      - + PlayerMessage.Target
      A target for messages.
      - + PlayerNotificationManager
      Starts, updates and cancels a media style notification reflecting the player state.
      - + PlayerNotificationManager.Builder
      A builder for PlayerNotificationManager instances.
      - + PlayerNotificationManager.CustomActionReceiver
      Defines and handles custom actions.
      - + PlayerNotificationManager.MediaDescriptionAdapter
      An adapter to provide content assets of the media currently playing.
      - + PlayerNotificationManager.NotificationListener
      A listener for changes to the notification.
      - + PlayerNotificationManager.Priority
      Priority of the notification (required for API 25 and lower).
      - + PlayerNotificationManager.Visibility
      Visibility of notification on the lock screen.
      - + PlayerView Deprecated.
      Use StyledPlayerView instead.
      - + PlayerView.ShowBuffering
      Determines when the buffering view is shown.
      - + PositionHolder
      Holds a position in the stream.
      - + Presentation
      Controls how a frame is presented with options to set the output resolution and choose how to map @@ -5697,646 +5716,646 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); match the specified output frame, or fitting the input frame using letterboxing).
      - + Presentation.Layout
      Strategies controlling the layout of input pixels in the output frame.
      - + PriorityDataSource
      A DataSource that can be used as part of a task registered with a PriorityTaskManager.
      - + PriorityDataSource.Factory - + PriorityDataSourceFactory Deprecated. - + PriorityTaskManager
      Allows tasks with associated priorities to control how they proceed relative to one another.
      - + PriorityTaskManager.PriorityTooLowException
      Thrown when task attempts to proceed when another registered task has a higher priority.
      - + PrivateCommand
      Represents a private command as defined in SCTE35, Section 9.3.6.
      - + PrivFrame
      PRIV (Private) ID3 frame.
      - + ProgramInformation
      A parsed program information element.
      - + ProgressHolder
      Holds a progress percentage.
      - + ProgressiveDownloader
      A downloader for progressive media streams.
      - + ProgressiveMediaExtractor
      Extracts the contents of a container file from a progressive media stream.
      - + ProgressiveMediaExtractor.Factory
      Creates ProgressiveMediaExtractor instances.
      - + ProgressiveMediaSource
      Provides one period that loads data from a Uri and extracted using an Extractor.
      - + ProgressiveMediaSource.Factory - + PsExtractor
      Extracts data from the MPEG-2 PS container format.
      - + PsshAtomUtil
      Utility methods for handling PSSH atoms.
      - + RandomizedMp3Decoder
      Generates randomized, but correct amount of data on MP3 audio input.
      - + RandomTrackSelection
      An ExoTrackSelection whose selected track is updated randomly.
      - + RandomTrackSelection.Factory
      Factory for RandomTrackSelection instances.
      - + RangedUri
      Defines a range of data located at a reference uri.
      - + Rating
      A rating for media content.
      - + RawResourceDataSource
      A DataSource for reading a raw resource inside the APK.
      - + RawResourceDataSource.RawResourceDataSourceException
      Thrown when an IOException is encountered reading from a raw resource.
      - + Renderer
      Renders media read from a SampleStream.
      - + Renderer.MessageType
      Represents a type of message that can be passed to a renderer.
      - + Renderer.State
      The renderer states.
      - + Renderer.WakeupListener
      Some renderers can signal when Renderer.render(long, long) should be called.
      - + RendererCapabilities
      Defines the capabilities of a Renderer.
      - + RendererCapabilities.AdaptiveSupport
      Level of renderer support for adaptive format switches.
      - + RendererCapabilities.Capabilities
      Combined renderer capabilities.
      - + RendererCapabilities.DecoderSupport
      Level of decoder support.
      - + RendererCapabilities.FormatSupport Deprecated.
      Use C.FormatSupport instead.
      - + RendererCapabilities.HardwareAccelerationSupport
      Level of renderer support for hardware acceleration.
      - + RendererCapabilities.TunnelingSupport
      Level of renderer support for tunneling.
      - + RendererConfiguration
      The configuration of a Renderer.
      - + RenderersFactory
      Builds Renderer instances for use by an ExoPlayer.
      - + RepeatModeActionProvider
      Provides a custom action for toggling repeat modes.
      - + RepeatModeUtil
      Util class for repeat mode handling.
      - + RepeatModeUtil.RepeatToggleModes
      Set of repeat toggle modes.
      - + Representation
      A DASH representation.
      - + Representation.MultiSegmentRepresentation
      A DASH representation consisting of multiple segments.
      - + Representation.SingleSegmentRepresentation
      A DASH representation consisting of a single segment.
      - + Requirements
      Defines a set of device state requirements.
      - + Requirements.RequirementFlags
      Requirement flags.
      - + RequirementsWatcher
      Watches whether the Requirements are met and notifies the RequirementsWatcher.Listener on changes.
      - + RequirementsWatcher.Listener
      Notified when RequirementsWatcher instance first created and on changes whether the Requirements are met.
      - + ResolvingDataSource
      DataSource wrapper allowing just-in-time resolution of DataSpecs.
      - + ResolvingDataSource.Factory - + ResolvingDataSource.Resolver
      Resolves DataSpecs.
      - + RgbAdjustment
      Scales the red, green, and blue color channels of a frame.
      - + RgbAdjustment.Builder
      A builder for RgbAdjustment instances.
      - + RgbFilter
      Provides common color filters.
      - + RgbMatrix
      Specifies a 4x4 RGB color transformation matrix to apply to each frame in the fragment shader.
      - + RobolectricUtil
      Utility methods for Robolectric-based tests.
      - + RtmpDataSource
      A Real-Time Messaging Protocol (RTMP) DataSource.
      - + RtmpDataSource.Factory - + RtmpDataSourceFactory Deprecated. - + RtpAc3Reader
      Parses an AC3 byte stream carried on RTP packets, and extracts AC3 frames.
      - + RtpPacket
      Represents the header and the payload of an RTP packet.
      - + RtpPacket.Builder
      Builder class for an RtpPacket
      - + RtpPayloadFormat
      Represents the payload format used in RTP.
      - + RtpPayloadReader
      Extracts media samples from the payload of received RTP packets.
      - + RtpPayloadReader.Factory
      Factory of RtpPayloadReader instances.
      - + RtpPcmReader
      Parses byte stream carried on RTP packets, and extracts PCM frames.
      - + RtpUtils
      Utility methods for RTP.
      - + RtspMediaSource
      An Rtsp MediaSource
      - + RtspMediaSource.Factory
      Factory for RtspMediaSource
      - + RtspMediaSource.RtspPlaybackException
      Thrown when an exception or error is encountered during loading an RTSP stream.
      - + RubySpan
      A styling span for ruby text.
      - + RunnableFutureTask<R,​E extends Exception>
      A RunnableFuture that supports additional uninterruptible operations to query whether execution has started and finished.
      - + SampleQueue
      A queue of media samples.
      - + SampleQueue.UpstreamFormatChangedListener
      A listener for changes to the upstream format.
      - + SampleQueueMappingException
      Thrown when it is not possible to map a TrackGroup to a SampleQueue.
      - + SampleStream
      A stream of media samples (and associated format information).
      - + SampleStream.ReadDataResult - + SampleStream.ReadFlags - + ScaleToFitTransformation
      Specifies a simple rotation and/or scale to apply in the vertex shader.
      - + ScaleToFitTransformation.Builder
      A builder for ScaleToFitTransformation instances.
      - + Scheduler
      Schedules a service to be started in the foreground when some Requirements are met.
      - + SectionPayloadReader
      Reads section data.
      - + SectionReader
      Reads section data packets and feeds the whole sections to a given SectionPayloadReader.
      - + SeekMap
      Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
      - + SeekMap.SeekPoints
      Contains one or two SeekPoints.
      - + SeekMap.Unseekable
      A SeekMap that does not support seeking.
      - + SeekParameters
      Parameters that apply to seeking.
      - + SeekPoint
      Defines a seek point in a media stream.
      - + SegmentBase
      An approximate representation of a SegmentBase manifest element.
      - + SegmentBase.MultiSegmentBase
      A SegmentBase that consists of multiple segments.
      - + SegmentBase.SegmentList
      A SegmentBase.MultiSegmentBase that uses a SegmentList to define its segments.
      - + SegmentBase.SegmentTemplate
      A SegmentBase.MultiSegmentBase that uses a SegmentTemplate to define its segments.
      - + SegmentBase.SegmentTimelineElement
      Represents a timeline segment from the MPD's SegmentTimeline list.
      - + SegmentBase.SingleSegmentBase
      A SegmentBase that defines a single segment.
      - + SegmentDownloader<M extends FilterableManifest<M>>
      Base class for multi segment stream downloaders.
      - + SegmentDownloader.Segment
      Smallest unit of content to be downloaded.
      - + SeiReader
      Consumes SEI buffers, outputting contained CEA-608/708 messages to a TrackOutput.
      - + SequenceableLoader
      A loader that can proceed in approximate synchronization with other loaders.
      - + SequenceableLoader.Callback<T extends SequenceableLoader>
      A callback to be notified of SequenceableLoader events.
      - + ServerSideAdInsertionMediaSource
      A MediaSource for server-side inserted ad breaks.
      - + ServerSideAdInsertionMediaSource.AdPlaybackStateUpdater
      Receives ad playback state update requests when the Timeline of the content media source has changed.
      - + ServerSideAdInsertionUtil
      A static utility class with methods to work with server-side inserted ads.
      - + ServiceDescriptionElement
      Represents a service description element.
      - + SessionAvailabilityListener
      Listener of changes in the cast session availability.
      - + SessionCallbackBuilder
      Builds a MediaSession.SessionCallback with various collaborators.
      - + SessionCallbackBuilder.AllowedCommandProvider
      Provides allowed commands for MediaController.
      - + SessionCallbackBuilder.CustomCommandProvider
      Callbacks for querying what custom commands are supported, and for handling a custom command when a controller sends it.
      - + SessionCallbackBuilder.DefaultAllowedCommandProvider
      Default implementation of SessionCallbackBuilder.AllowedCommandProvider that behaves as follows: @@ -6347,906 +6366,938 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Controller is in the same package as the session.
      - + SessionCallbackBuilder.DisconnectedCallback
      Callback for handling controller disconnection.
      - + SessionCallbackBuilder.MediaIdMediaItemProvider
      A SessionCallbackBuilder.MediaItemProvider that creates media items containing only a media ID.
      - + SessionCallbackBuilder.MediaItemProvider
      Provides the MediaItem.
      - + SessionCallbackBuilder.PostConnectCallback
      Callback for handling extra initialization after the connection.
      - + SessionCallbackBuilder.RatingCallback
      Callback receiving a user rating for a specified media id.
      - + SessionCallbackBuilder.SkipCallback
      Callback receiving skip backward and skip forward.
      - + SessionPlayerConnector
      An implementation of SessionPlayer that wraps a given ExoPlayer Player instance.
      - + ShadowMediaCodecConfig
      A JUnit @Rule to configure Roboelectric's ShadowMediaCodec.
      - + ShuffleOrder
      Shuffled order of indices.
      - + ShuffleOrder.DefaultShuffleOrder
      The default ShuffleOrder implementation for random shuffle order.
      - + ShuffleOrder.UnshuffledShuffleOrder
      A ShuffleOrder implementation which does not shuffle.
      - + SilenceMediaSource
      Media source with a single period consisting of silent raw audio of a given duration.
      - + SilenceMediaSource.Factory
      Factory for SilenceMediaSources.
      - + SilenceSkippingAudioProcessor
      An AudioProcessor that skips silence in the input stream.
      - + SimpleBasePlayer
      A base implementation for Player that reduces the number of methods to implement to a minimum.
      - + +SimpleBasePlayer.MediaItemData + +
      An immutable description of an item in the playlist, containing both static setup information + like MediaItem and dynamic data that is generally read from the media like the + duration.
      + + + +SimpleBasePlayer.MediaItemData.Builder + +
      A builder for SimpleBasePlayer.MediaItemData objects.
      + + + +SimpleBasePlayer.PeriodData + +
      Data describing the properties of a period inside a SimpleBasePlayer.MediaItemData.
      + + + +SimpleBasePlayer.PeriodData.Builder + +
      A builder for SimpleBasePlayer.PeriodData objects.
      + + + +SimpleBasePlayer.PositionSupplier + +
      A supplier for a position.
      + + + SimpleBasePlayer.State
      An immutable state description of the player.
      - + SimpleBasePlayer.State.Builder
      A builder for SimpleBasePlayer.State objects.
      - + SimpleCache
      A Cache implementation that maintains an in-memory representation.
      - + SimpleDecoder<I extends DecoderInputBuffer,​O extends DecoderOutputBuffer,​E extends DecoderException>
      Base class for Decoders that use their own decode thread and decode each input buffer immediately into a corresponding output buffer.
      - + SimpleDecoderOutputBuffer
      Buffer for SimpleDecoder output.
      - + SimpleExoPlayer Deprecated.
      Use ExoPlayer instead.
      - + SimpleExoPlayer.Builder Deprecated.
      Use ExoPlayer.Builder instead.
      - + SimpleMetadataDecoder
      A MetadataDecoder base class that validates input buffers and discards any for which Buffer.isDecodeOnly() is true.
      - + SimpleSubtitleDecoder
      Base class for subtitle parsers that use their own decode thread.
      - + SingleColorLut
      Transforms the colors of a frame by applying the same color lookup table to each frame.
      - + SingleFrameGlTextureProcessor
      Manages a GLSL shader program for processing a frame.
      - + SinglePeriodAdTimeline
      A Timeline for sources that have ads.
      - + SinglePeriodTimeline
      A Timeline consisting of a single period and static window.
      - + SingleSampleMediaChunk
      A BaseMediaChunk for chunks consisting of a single raw sample.
      - + SingleSampleMediaSource
      Loads data at a given Uri as a single sample belonging to a single MediaPeriod.
      - + SingleSampleMediaSource.Factory - + Size
      Immutable class for describing width and height dimensions in pixels.
      - + SlidingPercentile
      Calculate any percentile over a sliding window of weighted values.
      - + SlowMotionData
      Holds information about the segments of slow motion playback within a track.
      - + SlowMotionData.Segment
      Holds information about a single segment of slow motion playback within a track.
      - + SmtaMetadataEntry
      Stores metadata from the Samsung smta box.
      - + SntpClient
      Static utility to retrieve the device time offset using SNTP.
      - + SntpClient.InitializationCallback - + SonicAudioProcessor
      An AudioProcessor that uses the Sonic library to modify audio speed/pitch/sample rate.
      - + SpannedSubject
      A Truth Subject for assertions on Spanned instances containing text styling.
      - + SpannedSubject.AbsoluteSized
      Allows assertions about the absolute size of a span.
      - + SpannedSubject.Aligned
      Allows assertions about the alignment of a span.
      - + SpannedSubject.AndSpanFlags
      Allows additional assertions to be made on the flags of matching spans.
      - + SpannedSubject.Colored
      Allows assertions about the color of a span.
      - + SpannedSubject.EmphasizedText
      Allows assertions about a span's text emphasis mark and its position.
      - + SpannedSubject.RelativeSized
      Allows assertions about the relative size of a span.
      - + SpannedSubject.RubyText
      Allows assertions about a span's ruby text and its position.
      - + SpannedSubject.Typefaced
      Allows assertions about the typeface of a span.
      - + SpannedSubject.WithSpanFlags
      Allows additional assertions to be made on the flags of matching spans.
      - + SpanUtil
      Utility methods for Android span styling.
      - + SphericalGLSurfaceView
      Renders a GL scene in a non-VR Activity that is affected by phone orientation and touch input.
      - + SphericalGLSurfaceView.VideoSurfaceListener
      Listener for the Surface to which video frames should be rendered.
      - + SpliceCommand
      Superclass for SCTE35 splice commands.
      - + SpliceInfoDecoder
      Decodes splice info sections and produces splice commands.
      - + SpliceInsertCommand
      Represents a splice insert command defined in SCTE35, Section 9.3.3.
      - + SpliceInsertCommand.ComponentSplice
      Holds splicing information for specific splice insert command components.
      - + SpliceNullCommand
      Represents a splice null command as defined in SCTE35, Section 9.3.1.
      - + SpliceScheduleCommand
      Represents a splice schedule command as defined in SCTE35, Section 9.3.2.
      - + SpliceScheduleCommand.ComponentSplice
      Holds splicing information for specific splice schedule command components.
      - + SpliceScheduleCommand.Event
      Represents a splice event as contained in a SpliceScheduleCommand.
      - + SsaDecoder
      A SimpleSubtitleDecoder for SSA/ASS.
      - + SsChunkSource
      A ChunkSource for SmoothStreaming.
      - + SsChunkSource.Factory
      Factory for SsChunkSources.
      - + SsDownloader
      A downloader for SmoothStreaming streams.
      - + SsManifest
      Represents a SmoothStreaming manifest.
      - + SsManifest.ProtectionElement
      Represents a protection element containing a single header.
      - + SsManifest.StreamElement
      Represents a StreamIndex element.
      - + SsManifestParser
      Parses SmoothStreaming client manifests.
      - + SsManifestParser.MissingFieldException
      Thrown if a required field is missing.
      - + SsMediaSource
      A SmoothStreaming MediaSource.
      - + SsMediaSource.Factory
      Factory for SsMediaSource.
      - + StandaloneDatabaseProvider
      An SQLiteOpenHelper that provides instances of a standalone database.
      - + StandaloneMediaClock
      A MediaClock whose position advances with real time based on the playback parameters when started.
      - + StarRating
      A rating expressed as a fractional number of stars.
      - + StartOffsetExtractorOutput
      An extractor output that wraps another extractor output and applies a give start byte offset to seek positions.
      - + StatsDataSource
      DataSource wrapper which keeps track of bytes transferred, redirected uris, and response headers.
      - + StreamKey
      A key for a subset of media that can be separately loaded (a "stream").
      - + StubExoPlayer
      An abstract ExoPlayer implementation that throws UnsupportedOperationException from every method.
      - + StubPlayer
      An abstract Player implementation that throws UnsupportedOperationException from every method.
      - + StyledPlayerControlView
      A view for controlling Player instances.
      - + StyledPlayerControlView.OnFullScreenModeChangedListener Deprecated. - + StyledPlayerControlView.ProgressUpdateListener
      Listener to be notified when progress has been updated.
      - + StyledPlayerControlView.VisibilityListener Deprecated. - + StyledPlayerView
      A high level view for Player media playbacks.
      - + StyledPlayerView.ControllerVisibilityListener
      Listener to be notified about changes of the visibility of the UI controls.
      - + StyledPlayerView.FullscreenButtonClickListener
      Listener invoked when the fullscreen button is clicked.
      - + StyledPlayerView.ShowBuffering
      Determines when the buffering view is shown.
      - + SubripDecoder
      A SimpleSubtitleDecoder for SubRip.
      - + Subtitle
      A subtitle consisting of timed Cues.
      - + SubtitleDecoder - + SubtitleDecoderException
      Thrown when an error occurs decoding subtitle data.
      - + SubtitleDecoderFactory
      A factory for SubtitleDecoder instances.
      - + SubtitleExtractor
      Generic extractor for extracting subtitles from various subtitle formats.
      - + SubtitleInputBuffer - + SubtitleOutputBuffer
      Base class for SubtitleDecoder output buffers.
      - + SubtitleView
      A view for displaying subtitle Cues.
      - + SubtitleView.ViewType
      The type of View to use to display subtitles.
      - + SurfaceInfo
      Immutable value class for a Surface and supporting information.
      - + SynchronousMediaCodecAdapter
      A MediaCodecAdapter that operates the underlying MediaCodec in synchronous mode.
      - + SynchronousMediaCodecAdapter.Factory
      A factory for SynchronousMediaCodecAdapter instances.
      - + SystemClock
      The standard implementation of Clock, an instance of which is available via Clock.DEFAULT.
      - + TeeAudioProcessor
      Audio processor that outputs its input unmodified and also outputs its input to a given sink.
      - + TeeAudioProcessor.AudioBufferSink
      A sink for audio buffers handled by the audio processor.
      - + TeeAudioProcessor.WavFileAudioBufferSink
      A sink for audio buffers that writes output audio as .wav files with a given path prefix.
      - + TeeDataSource
      Tees data into a DataSink as the data is read.
      - + TestDownloadManagerListener
      Allows tests to block for, and assert properties of, calls from a DownloadManager to its DownloadManager.Listener.
      - + TestExoPlayerBuilder
      A builder of ExoPlayer instances for testing.
      - + TestPlayerRunHelper
      Helper methods to block the calling thread until the provided ExoPlayer instance reaches a particular state.
      - + TestUtil
      Utility methods for tests.
      - + TextAnnotation
      Properties of a text annotation (i.e.
      - + TextAnnotation.Position
      The possible positions of the annotation text relative to the base text.
      - + TextEmphasisSpan
      A styling span for text emphasis marks.
      - + TextEmphasisSpan.MarkFill
      The possible mark fills that can be used.
      - + TextEmphasisSpan.MarkShape
      The possible mark shapes that can be used.
      - + TextInformationFrame
      Text information ID3 frame.
      - + TextOutput
      Receives text output.
      - + TextRenderer
      A renderer for text.
      - + TextureInfo
      Contains information describing an OpenGL texture.
      - + ThumbRating
      A rating expressed as "thumbs up" or "thumbs down".
      - + TimeBar
      Interface for time bar views that can display a playback position, buffered position, duration and ad markers, and that have a listener for scrubbing (seeking) events.
      - + TimeBar.OnScrubListener
      Listener for scrubbing events.
      - + TimedValueQueue<V>
      A utility class to keep a queue of values with timestamps.
      - + Timeline
      A flexible representation of the structure of media.
      - + Timeline.Period
      Holds information about a period in a Timeline.
      - + Timeline.RemotableTimeline
      A concrete class of Timeline to restore a Timeline instance from a Bundle sent by another process via IBinder.
      - + Timeline.Window
      Holds information about a window in a Timeline.
      - + TimelineAsserts
      Assertion methods for Timeline.
      - + TimelineQueueEditor - + TimelineQueueEditor.MediaDescriptionConverter
      Converts a MediaDescriptionCompat to a MediaItem.
      - + TimelineQueueEditor.MediaIdEqualityChecker
      Media description comparator comparing the media IDs.
      - + TimelineQueueEditor.QueueDataAdapter
      Adapter to get MediaDescriptionCompat of items in the queue and to notify the application about changes in the queue to sync the data structure backing the MediaSessionConnector.
      - + TimelineQueueNavigator
      An abstract implementation of the MediaSessionConnector.QueueNavigator that maps the windows of a Player's Timeline to the media session queue.
      - + TimeSignalCommand
      Represents a time signal command as defined in SCTE35, Section 9.3.4.
      - + TimestampAdjuster
      Adjusts and offsets sample timestamps.
      - + TimestampAdjusterProvider
      Provides TimestampAdjuster instances for use during HLS playbacks.
      - + TimeToFirstByteEstimator
      Provides an estimate of the time to first byte of a transfer.
      - + TraceUtil
      Calls through to Trace methods on supported API levels.
      - + Track
      Encapsulates information describing an MP4 track.
      - + Track.Transformation
      The transformation to apply to samples in the track, if any.
      - + TrackEncryptionBox
      Encapsulates information parsed from a track encryption (tenc) box or sample group description (sgpd) box in an MP4 stream.
      - + TrackGroup
      An immutable group of tracks available within a media stream.
      - + TrackGroupArray
      An immutable array of TrackGroups.
      - + TrackNameProvider
      Converts Formats to user readable track names.
      - + TrackOutput
      Receives track level data extracted by an Extractor.
      - + TrackOutput.CryptoData
      Holds data required to decrypt a sample.
      - + TrackOutput.SampleDataPart
      Defines the part of the sample data to which a call to TrackOutput.sampleData(com.google.android.exoplayer2.upstream.DataReader, int, boolean) corresponds.
      - + Tracks
      Information about groups of tracks.
      - + Tracks.Group
      Information about a single group of tracks, including the underlying TrackGroup, the @@ -7254,544 +7305,544 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); selected.
      - + TrackSelection
      A track selection consisting of a static subset of selected tracks belonging to a TrackGroup.
      - + TrackSelection.Type
      Represents a type track selection.
      - + TrackSelectionArray
      An array of TrackSelections.
      - + TrackSelectionDialogBuilder
      Builder for a dialog with a TrackSelectionView.
      - + TrackSelectionDialogBuilder.DialogCallback
      Callback which is invoked when a track selection has been made.
      - + TrackSelectionOverride
      A track selection override, consisting of a TrackGroup and the indices of the tracks within the group that should be selected.
      - + TrackSelectionParameters
      Parameters for controlling track selection.
      - + TrackSelectionParameters.Builder - + TrackSelectionUtil
      Track selection related utility methods.
      - + TrackSelectionUtil.AdaptiveTrackSelectionFactory
      Functional interface to create a single adaptive track selection.
      - + TrackSelectionView
      A view for making track selections.
      - + TrackSelectionView.TrackSelectionListener
      Listener for changes to the selected tracks.
      - + TrackSelector
      The component of an ExoPlayer responsible for selecting tracks to be consumed by each of the player's Renderers.
      - + TrackSelector.InvalidationListener
      Notified when selections previously made by a TrackSelector are no longer valid.
      - + TrackSelectorResult
      The result of a TrackSelector operation.
      - + TransferListener
      A listener of data transfer events.
      - + TransformationException
      Thrown when a non-locally recoverable transformation failure occurs.
      - + TransformationException.ErrorCode
      Codes that identify causes of Transformer errors.
      - + TransformationRequest
      A media transformation request.
      - + TransformationRequest.Builder
      A builder for TransformationRequest instances.
      - + TransformationResult
      Information about the result of a successful transformation.
      - + TransformationResult.Builder
      A builder for TransformationResult instances.
      - + Transformer
      A transformer to transform media inputs.
      - + Transformer.Builder
      A builder for Transformer instances.
      - + Transformer.Listener
      A listener for the transformation events.
      - + Transformer.ProgressState
      Progress state.
      - + TrueHdSampleRechunker
      Rechunks TrueHD sample data into groups of Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT samples.
      - + TsExtractor
      Extracts data from the MPEG-2 TS container format.
      - + TsExtractor.Mode
      Modes for the extractor.
      - + TsPayloadReader
      Parses TS packet payload data.
      - + TsPayloadReader.DvbSubtitleInfo
      Holds information about a DVB subtitle, as defined in ETSI EN 300 468 V1.11.1 section 6.2.41.
      - + TsPayloadReader.EsInfo
      Holds information associated with a PMT entry.
      - + TsPayloadReader.Factory
      Factory of TsPayloadReader instances.
      - + TsPayloadReader.Flags
      Contextual flags indicating the presence of indicators in the TS packet or PES packet headers.
      - + TsPayloadReader.TrackIdGenerator
      Generates track ids for initializing TsPayloadReaders' TrackOutputs.
      - + TsUtil
      Utilities method for extracting MPEG-TS streams.
      - + TtmlDecoder
      A SimpleSubtitleDecoder for TTML supporting the DFXP presentation profile.
      - + Tx3gDecoder - + UdpDataSource
      A UDP DataSource.
      - + UdpDataSource.UdpDataSourceException
      Thrown when an error is encountered when trying to read from a UdpDataSource.
      - + UnknownNull
      Annotation for specifying unknown nullness.
      - + UnrecognizedInputFormatException
      Thrown if the input format was not recognized.
      - + UnsupportedDrmException
      Thrown when the requested DRM scheme is not supported.
      - + UnsupportedDrmException.Reason
      The reason for the exception.
      - + UriUtil
      Utility methods for manipulating URIs.
      - + UrlLinkFrame
      Url link ID3 frame.
      - + UrlTemplate
      A template from which URLs can be built.
      - + UtcTimingElement
      Represents a UTCTiming element.
      - + Util
      Miscellaneous utility methods.
      - + VersionTable
      Utility methods for accessing versions of media library database components.
      - + VideoDecoderGLSurfaceView
      GLSurfaceView implementing VideoDecoderOutputBufferRenderer for rendering VideoDecoderOutputBuffers.
      - + VideoDecoderOutputBuffer
      Video decoder output buffer containing video frame data.
      - + VideoDecoderOutputBufferRenderer - + VideoEncoderSettings
      Represents the video encoder settings.
      - + VideoEncoderSettings.BitrateMode
      The allowed values for bitrateMode.
      - + VideoEncoderSettings.Builder
      Builds VideoEncoderSettings instances.
      - + VideoFrameMetadataListener
      A listener for metadata corresponding to video frames being rendered.
      - + VideoFrameReleaseHelper
      Helps a video Renderer release frames to a Surface.
      - + VideoRendererEventListener
      Listener of video Renderer events.
      - + VideoRendererEventListener.EventDispatcher
      Dispatches events to a VideoRendererEventListener.
      - + VideoSize
      Represents the video size.
      - + VorbisBitArray
      Wraps a byte array, providing methods that allow it to be read as a Vorbis bitstream.
      - + VorbisComment Deprecated.
      Use VorbisComment instead.
      - + VorbisComment
      A vorbis comment, extracted from a FLAC or Ogg file.
      - + VorbisUtil
      Utility methods for parsing Vorbis streams.
      - + VorbisUtil.CommentHeader
      Vorbis comment header.
      - + VorbisUtil.Mode
      Vorbis setup header modes.
      - + VorbisUtil.VorbisIdHeader
      Vorbis identification header.
      - + VpxDecoder
      Vpx decoder.
      - + VpxDecoderException
      Thrown when a libvpx decoder error occurs.
      - + VpxLibrary
      Configures and queries the underlying native library.
      - + WavExtractor
      Extracts data from WAV byte streams.
      - + WavUtil
      Utilities for handling WAVE files.
      - + WebServerDispatcher
      A Dispatcher for MockWebServer that allows per-path customisation of the static data served.
      - + WebServerDispatcher.Resource
      A resource served by WebServerDispatcher.
      - + WebServerDispatcher.Resource.Builder - + WebvttCssStyle
      Style object of a Css style block in a Webvtt file.
      - + WebvttCssStyle.FontSizeUnit
      Font size unit enum.
      - + WebvttCssStyle.StyleFlags
      Style flag enum.
      - + WebvttCueInfo
      A representation of a WebVTT cue.
      - + WebvttCueParser
      Parser for WebVTT cues.
      - + WebvttDecoder
      A SimpleSubtitleDecoder for WebVTT.
      - + WebvttExtractor
      A special purpose extractor for WebVTT content in HLS.
      - + WebvttParserUtil
      Utility methods for parsing WebVTT data.
      - + WidevineUtil
      Utility methods for Widevine.
      - + WorkManagerScheduler
      A Scheduler that uses WorkManager.
      - + WorkManagerScheduler.SchedulerWorker
      A Worker that starts the target service if the requirements are met.
      - + WrappingMediaSource
      An abstract MediaSource wrapping a single child MediaSource.
      - + WritableDownloadIndex
      A writable index of Downloads.
      - + XmlPullParserUtil
      XmlPullParser utility methods.
      diff --git a/docs/doc/reference/allclasses.html b/docs/doc/reference/allclasses.html index e33a5d8aa3..55fdc38cb9 100644 --- a/docs/doc/reference/allclasses.html +++ b/docs/doc/reference/allclasses.html @@ -256,6 +256,8 @@
    • CompositeSequenceableLoader
    • CompositeSequenceableLoaderFactory
    • ConcatenatingMediaSource
    • +
    • ConcatenatingMediaSource2
    • +
    • ConcatenatingMediaSource2.Builder
    • ConditionVariable
    • ConstantBitrateSeekMap
    • Consumer
    • @@ -796,6 +798,7 @@
    • MediaMetadata
    • MediaMetadata.Builder
    • MediaMetadata.FolderType
    • +
    • MediaMetadata.MediaType
    • MediaMetadata.PictureType
    • MediaMetricsListener
    • MediaParserChunkExtractor
    • @@ -1058,6 +1061,11 @@
    • SilenceMediaSource.Factory
    • SilenceSkippingAudioProcessor
    • SimpleBasePlayer
    • +
    • SimpleBasePlayer.MediaItemData
    • +
    • SimpleBasePlayer.MediaItemData.Builder
    • +
    • SimpleBasePlayer.PeriodData
    • +
    • SimpleBasePlayer.PeriodData.Builder
    • +
    • SimpleBasePlayer.PositionSupplier
    • SimpleBasePlayer.State
    • SimpleBasePlayer.State.Builder
    • SimpleCache
    • diff --git a/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html index 6dfc96dea1..1a1f52797c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/AbstractConcatenatedTimeline.html @@ -380,7 +380,7 @@ extends T

      Methods inherited from class com.google.android.exoplayer2.Timeline

      -equals, getNextPeriodIndex, getPeriod, getPeriodCount, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, getWindowCount, hashCode, isEmpty, isLastPeriod, toBundle, toBundle +equals, getNextPeriodIndex, getPeriod, getPeriodCount, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, getWindowCount, hashCode, isEmpty, isLastPeriod, toBundle, toBundleWithOneWindowOnly @@ -734,7 +745,9 @@ implements public final void setMediaItem​(MediaItem mediaItem)
      Description copied from interface: Player
      Clears the playlist, adds the specified MediaItem and resets the position to the - default position.
      + default position. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEM is available.

      Specified by:
      setMediaItem in interface Player
      @@ -752,7 +765,9 @@ implements public final void setMediaItem​(MediaItem mediaItem, long startPositionMs)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItem.
      +
      Clears the playlist and adds the specified MediaItem. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEM is available.

      Specified by:
      setMediaItem in interface Player
      @@ -771,7 +786,9 @@ implements public final void setMediaItem​(MediaItem mediaItem, boolean resetPosition)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItem.
      +
      Clears the playlist and adds the specified MediaItem. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEM is available.

      Specified by:
      setMediaItem in interface Player
      @@ -791,13 +808,15 @@ implements public final void setMediaItems​(List<MediaItem> mediaItems)
      Description copied from interface: Player
      -
      Clears the playlist, adds the specified MediaItems and resets the position to - the default position.
      +
      Clears the playlist, adds the specified media items and resets the + position to the default position. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      @@ -810,7 +829,9 @@ implements public final void addMediaItem​(int index, MediaItem mediaItem)
      Description copied from interface: Player
      -
      Adds a media item at the given index of the playlist.
      +
      Adds a media item at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      addMediaItem in interface Player
      @@ -829,7 +850,9 @@ implements public final void addMediaItem​(MediaItem mediaItem)
      Description copied from interface: Player
      -
      Adds a media item to the end of the playlist.
      +
      Adds a media item to the end of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      addMediaItem in interface Player
      @@ -846,12 +869,14 @@ implements public final void addMediaItems​(List<MediaItem> mediaItems)
      Description copied from interface: Player
      -
      Adds a list of media items to the end of the playlist.
      +
      Adds a list of media items to the end of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      addMediaItems in interface Player
      Parameters:
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -864,12 +889,15 @@ implements public final void moveMediaItem​(int currentIndex, int newIndex) -
      Moves the media item at the current index to the new index.
      +
      Moves the media item at the current index to the new index. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      moveMediaItem in interface Player
      Parameters:
      -
      currentIndex - The current index of the media item to move.
      +
      currentIndex - The current index of the media item to move. If the index is larger than + the size of the playlist, the request is ignored.
      newIndex - The new index of the media item. If the new index is larger than the size of the playlist the item is moved to the end of the playlist.
      @@ -883,12 +911,15 @@ implements public final void removeMediaItem​(int index) -
      Removes the media item at the given index of the playlist.
      +
      Removes the media item at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      removeMediaItem in interface Player
      Parameters:
      -
      index - The index at which to remove the media item.
      +
      index - The index at which to remove the media item. If the index is larger than the size + of the playlist, the request is ignored.
      @@ -900,7 +931,9 @@ implements public final void clearMediaItems() -
      Clears the playlist.
      +
      Clears the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      clearMediaItems in interface Player
      @@ -918,13 +951,7 @@ implements Description copied from interface: Player
      Returns whether the provided Player.Command is available. -

      This method does not execute the command. - -

      Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a Player.getPlayerError() player error}. - -

      Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem.

      +

      This method does not execute the command.

      Specified by:
      isCommandAvailable in interface Player
      @@ -962,7 +989,9 @@ implements public final void play()
      Resumes playback as soon as Player.getPlaybackState() == Player.STATE_READY. Equivalent to - setPlayWhenReady(true).
      + setPlayWhenReady(true). + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Specified by:
      play in interface Player
      @@ -977,7 +1006,9 @@ implements public final void pause() -
      Pauses playback. Equivalent to setPlayWhenReady(false).
      +
      Pauses playback. Equivalent to setPlayWhenReady(false). + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Specified by:
      pause in interface Player
      @@ -1021,7 +1052,9 @@ implements Description copied from interface: Player
      Seeks to the default position associated with the current MediaItem. The position can depend on the type of media being played. For live streams it will typically be the live edge. - For other streams it will typically be the start.
      + For other streams it will typically be the start. + +

      This method must only be called if Player.COMMAND_SEEK_TO_DEFAULT_POSITION is available.

      Specified by:
      seekToDefaultPosition in interface Player
      @@ -1038,31 +1071,16 @@ implements Description copied from interface: Player
      Seeks to the default position associated with the specified MediaItem. The position can depend on the type of media being played. For live streams it will typically be the live edge. - For other streams it will typically be the start.
      + For other streams it will typically be the start. + +

      This method must only be called if Player.COMMAND_SEEK_TO_MEDIA_ITEM is available.

      Specified by:
      seekToDefaultPosition in interface Player
      Parameters:
      mediaItemIndex - The index of the MediaItem whose associated default position - should be seeked to.
      -
      - - - - - -
        -
      • -

        seekTo

        -
        public final void seekTo​(long positionMs)
        -
        Description copied from interface: Player
        -
        Seeks to a position specified in milliseconds in the current MediaItem.
        -
        -
        Specified by:
        -
        seekTo in interface Player
        -
        Parameters:
        -
        positionMs - The seek position in the current MediaItem, or C.TIME_UNSET - to seek to the media item's default position.
        + should be seeked to. If the index is larger than the size of the playlist, the request is + ignored.
      @@ -1074,7 +1092,9 @@ implements public final void seekBack() -
      Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
      +
      Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds. + +

      This method must only be called if Player.COMMAND_SEEK_BACK is available.

      Specified by:
      seekBack in interface Player
      @@ -1090,7 +1110,9 @@ implements public final void seekForward()
      Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() - milliseconds.
      + milliseconds. + +

      This method must only be called if Player.COMMAND_SEEK_FORWARD is available.

      Specified by:
      seekForward in interface Player
      @@ -1144,7 +1166,9 @@ public final boolean hasPreviousWindow()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      hasPreviousMediaItem in interface Player
      @@ -1198,7 +1222,10 @@ public final void seekToPreviousWindow()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM is + available.

      Specified by:
      seekToPreviousMediaItem in interface Player
      @@ -1227,7 +1254,9 @@ public final void seekToPreviousWindow()
    • Otherwise, if a previous media item exists and the current position is less than Player.getMaxSeekToPreviousPosition(), seeks to the default position of the previous MediaItem.
    • Otherwise, seeks to 0 in the current MediaItem. - + + +

      This method must only be called if Player.COMMAND_SEEK_TO_PREVIOUS is available.

      Specified by:
      seekToPrevious in interface Player
      @@ -1281,7 +1310,9 @@ public final boolean hasNextWindow()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      hasNextMediaItem in interface Player
      @@ -1336,7 +1367,9 @@ public final void seekToNextWindow()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is available.

      Specified by:
      seekToNextMediaItem in interface Player
      @@ -1361,13 +1394,81 @@ public final void seekToNextWindow()
    • Otherwise, if the current MediaItem is live and has not ended, seeks to the live edge of the current MediaItem.
    • Otherwise, does nothing. - + + +

      This method must only be called if Player.COMMAND_SEEK_TO_NEXT is available.

      Specified by:
      seekToNext in interface Player
    • + + + + + + + +
        +
      • +

        seekTo

        +
        public final void seekTo​(int mediaItemIndex,
        +                         long positionMs)
        +
        Description copied from interface: Player
        +
        Seeks to a position specified in milliseconds in the specified MediaItem. + +

        This method must only be called if Player.COMMAND_SEEK_TO_MEDIA_ITEM is available.

        +
        +
        Specified by:
        +
        seekTo in interface Player
        +
        Parameters:
        +
        mediaItemIndex - The index of the MediaItem. If the index is larger than the size + of the playlist, the request is ignored.
        +
        positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET + to seek to the media item's default position.
        +
        +
      • +
      + + + +
        +
      • +

        seekTo

        +
        public abstract void seekTo​(int mediaItemIndex,
        +                            long positionMs,
        +                            @Command
        +                            @com.google.android.exoplayer2.Player.Command int seekCommand,
        +                            boolean isRepeatingCurrentItem)
        +
        Seeks to a position in the specified MediaItem.
        +
        +
        Parameters:
        +
        mediaItemIndex - The index of the MediaItem.
        +
        positionMs - The seek position in the specified MediaItem in milliseconds, or + C.TIME_UNSET to seek to the media item's default position.
        +
        seekCommand - The Player.Command used to trigger the seek.
        +
        isRepeatingCurrentItem - Whether this seeks repeats the current item.
        +
        +
      • +
      @@ -1379,13 +1480,15 @@ public final void seekToNextWindow()
      Changes the rate at which playback occurs. The pitch is not changed.

      This is equivalent to - setPlaybackParameters(getPlaybackParameters().withSpeed(speed)).

      + setPlaybackParameters(getPlaybackParameters().withSpeed(speed)). + +

      This method must only be called if Player.COMMAND_SET_SPEED_AND_PITCH is available.

      Specified by:
      setPlaybackSpeed in interface Player
      Parameters:
      speed - The linear factor by which playback will be sped up. Must be higher than 0. 1 is - normal speed, 2 is twice as fast, 0.5 is half normal speed...
      + normal speed, 2 is twice as fast, 0.5 is half normal speed.
    • @@ -1437,7 +1540,9 @@ public final int getNextWindowIndex()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getNextMediaItemIndex in interface Player
      @@ -1475,7 +1580,9 @@ public final int getPreviousWindowIndex()

      Note: When the repeat mode is Player.REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is Player.REPEAT_MODE_OFF. See Player.REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getPreviousMediaItemIndex in interface Player
      @@ -1491,7 +1598,9 @@ public final int getPreviousWindowIndex()
      @Nullable
       public final MediaItem getCurrentMediaItem()
      Description copied from interface: Player
      -
      Returns the currently playing MediaItem. May be null if the timeline is empty.
      +
      Returns the currently playing MediaItem. May be null if the timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getCurrentMediaItem in interface Player
      @@ -1508,7 +1617,9 @@ public final public final int getMediaItemCount() -
      Returns the number of media items in the playlist.
      +
      Returns the number of media items in the playlist. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getMediaItemCount in interface Player
      @@ -1523,7 +1634,9 @@ public final public final MediaItem getMediaItemAt​(int index)
      Description copied from interface: Player
      -
      Returns the MediaItem at the given index.
      +
      Returns the MediaItem at the given index. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getMediaItemAt in interface Player
      @@ -1588,7 +1701,9 @@ public final boolean isCurrentWindowDynamic()
      public final boolean isCurrentMediaItemDynamic()
      Description copied from interface: Player
      Returns whether the current MediaItem is dynamic (may change when the Timeline - is updated), or false if the Timeline is empty.
      + is updated), or false if the Timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      isCurrentMediaItemDynamic in interface Player
      @@ -1623,7 +1738,9 @@ public final boolean isCurrentWindowLive()
      public final boolean isCurrentMediaItemLive()
      Description copied from interface: Player
      Returns whether the current MediaItem is live, or false if the Timeline - is empty.
      + is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      isCurrentMediaItemLive in interface Player
      @@ -1648,7 +1765,9 @@ public final boolean isCurrentWindowLive() positive.

      Note that this offset may rely on an accurate local time, so this method may return an - incorrect value if the difference between system clock and server clock is unknown. + incorrect value if the difference between system clock and server clock is unknown. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getCurrentLiveOffset in interface Player
      @@ -1680,7 +1799,9 @@ public final boolean isCurrentWindowSeekable()

      isCurrentMediaItemSeekable

      public final boolean isCurrentMediaItemSeekable()
      Description copied from interface: Player
      -
      Returns whether the current MediaItem is seekable, or false if the Timeline is empty.
      +
      Returns whether the current MediaItem is seekable, or false if the Timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      isCurrentMediaItemSeekable in interface Player
      @@ -1692,34 +1813,22 @@ public final boolean isCurrentWindowSeekable() -
        + - - - -
          -
        • -

          repeatCurrentMediaItem

          -
          @ForOverride
          -protected void repeatCurrentMediaItem()
          -
          Repeat the current media item. - -

          The default implementation seeks to the default position in the current item, which can be - overridden for additional handling.

          -
        • -
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/C.Encoding.html b/docs/doc/reference/com/google/android/exoplayer2/C.Encoding.html index 955a149c7c..f8e43ba352 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/C.Encoding.html +++ b/docs/doc/reference/com/google/android/exoplayer2/C.Encoding.html @@ -120,7 +120,7 @@ public static @interface C.Encoding + C.ENCODING_DTS_HD, C.ENCODING_DOLBY_TRUEHD or C.ENCODING_OPUS. diff --git a/docs/doc/reference/com/google/android/exoplayer2/C.html b/docs/doc/reference/com/google/android/exoplayer2/C.html index 97d9c59ace..c394bbcb03 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/C.html +++ b/docs/doc/reference/com/google/android/exoplayer2/C.html @@ -880,46 +880,51 @@ extends static int -ENCODING_PCM_16BIT +ENCODING_OPUS   static int +ENCODING_PCM_16BIT +  + + +static int ENCODING_PCM_16BIT_BIG_ENDIAN
      Like ENCODING_PCM_16BIT, but with the bytes in big endian order.
      - + static int ENCODING_PCM_24BIT
      PCM encoding with 24 bits per sample.
      - + static int ENCODING_PCM_32BIT
      PCM encoding with 32 bits per sample.
      - + static int ENCODING_PCM_8BIT   - + static int ENCODING_PCM_FLOAT   - + static int FLAG_AUDIBILITY_ENFORCED   - + static int FORMAT_EXCEEDS_CAPABILITIES @@ -927,14 +932,14 @@ extends - + static int FORMAT_HANDLED
      The Renderer is capable of rendering the format.
      - + static int FORMAT_UNSUPPORTED_DRM @@ -942,7 +947,7 @@ extends - + static int FORMAT_UNSUPPORTED_SUBTYPE @@ -951,7 +956,7 @@ extends - + static int FORMAT_UNSUPPORTED_TYPE @@ -959,14 +964,14 @@ extends - + static int INDEX_UNSET
      Represents an unset or unknown index.
      - + static String ISO88591_NAME @@ -975,266 +980,266 @@ extends - + static String LANGUAGE_UNDETERMINED
      Represents an undetermined language as an ISO 639-2 language code.
      - + static int LENGTH_UNSET
      Represents an unset or unknown length.
      - + static long MICROS_PER_SECOND
      The number of microseconds in one second.
      - + static long MILLIS_PER_SECOND
      The number of milliseconds in one second.
      - + static long NANOS_PER_SECOND
      The number of nanoseconds in one second.
      - + static int NETWORK_TYPE_2G
      Network type for a 2G cellular connection.
      - + static int NETWORK_TYPE_3G
      Network type for a 3G cellular connection.
      - + static int NETWORK_TYPE_4G
      Network type for a 4G cellular connection.
      - + static int NETWORK_TYPE_5G_NSA
      Network type for a 5G non-stand-alone (NSA) cellular connection.
      - + static int NETWORK_TYPE_5G_SA
      Network type for a 5G stand-alone (SA) cellular connection.
      - + static int NETWORK_TYPE_CELLULAR_UNKNOWN
      Network type for cellular connections which cannot be mapped to one of NETWORK_TYPE_2G, NETWORK_TYPE_3G, or NETWORK_TYPE_4G.
      - + static int NETWORK_TYPE_ETHERNET
      Network type for an Ethernet connection.
      - + static int NETWORK_TYPE_OFFLINE
      No network connection.
      - + static int NETWORK_TYPE_OTHER
      Network type for other connections which are not Wifi or cellular (e.g.
      - + static int NETWORK_TYPE_UNKNOWN
      Unknown network type.
      - + static int NETWORK_TYPE_WIFI
      Network type for a Wifi connection.
      - + static int PERCENTAGE_UNSET
      Represents an unset or unknown percentage.
      - + static UUID PLAYREADY_UUID
      UUID for the PlayReady DRM scheme.
      - + static int POSITION_UNSET
      Represents an unset or unknown position.
      - + static int PRIORITY_DOWNLOAD
      Priority for media downloading.
      - + static int PRIORITY_PLAYBACK
      Priority for media playback.
      - + static int PROJECTION_CUBEMAP
      Cube map projection.
      - + static int PROJECTION_EQUIRECTANGULAR
      Equirectangular spherical projection.
      - + static int PROJECTION_MESH
      3-D mesh projection.
      - + static int PROJECTION_RECTANGULAR
      Conventional rectangular projection.
      - + static float RATE_UNSET
      Represents an unset or unknown rate.
      - + static int RATE_UNSET_INT
      Represents an unset or unknown integer rate.
      - + static int RESULT_BUFFER_READ
      A return value for methods where a buffer was read.
      - + static int RESULT_END_OF_INPUT
      A return value for methods where the end of an input was encountered.
      - + static int RESULT_FORMAT_READ
      A return value for methods where a format was read.
      - + static int RESULT_MAX_LENGTH_EXCEEDED
      A return value for methods where the length of parsed data exceeds the maximum length allowed.
      - + static int RESULT_NOTHING_READ
      A return value for methods where nothing was read.
      - + static int ROLE_FLAG_ALTERNATE
      Indicates an alternate track.
      - + static int ROLE_FLAG_CAPTION
      Indicates the track contains captions.
      - + static int ROLE_FLAG_COMMENTARY
      Indicates the track contains commentary, for example from the director.
      - + static int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND
      Indicates the track contains a textual description of music and sound.
      - + static int ROLE_FLAG_DESCRIBES_VIDEO
      Indicates the track contains an audio or textual description of a video track.
      - + static int ROLE_FLAG_DUB @@ -1242,77 +1247,77 @@ extends - + static int ROLE_FLAG_EASY_TO_READ
      Indicates the track contains a text that has been edited for ease of reading.
      - + static int ROLE_FLAG_EMERGENCY
      Indicates the track contains information about a current emergency.
      - + static int ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY
      Indicates the track is designed for improved intelligibility of dialogue.
      - + static int ROLE_FLAG_MAIN
      Indicates a main track.
      - + static int ROLE_FLAG_SIGN
      Indicates the track contains a visual sign-language interpretation of an audio track.
      - + static int ROLE_FLAG_SUBTITLE
      Indicates the track contains subtitles.
      - + static int ROLE_FLAG_SUPPLEMENTARY
      Indicates a supplementary track, meaning the track has lower importance than the main track(s).
      - + static int ROLE_FLAG_TRANSCRIBES_DIALOG
      Indicates the track contains a transcription of spoken dialog.
      - + static int ROLE_FLAG_TRICK_PLAY
      Indicates the track is intended for trick play.
      - + static String SANS_SERIF_NAME
      The name of the sans-serif font family.
      - + static int SELECTION_FLAG_AUTOSELECT @@ -1320,14 +1325,14 @@ extends - + static int SELECTION_FLAG_DEFAULT
      Indicates that the track should be selected if user preferences do not state otherwise.
      - + static int SELECTION_FLAG_FORCED @@ -1335,14 +1340,14 @@ extends - + static int SELECTION_REASON_ADAPTIVE
      A selection reason constant for an adaptive track selection.
      - + static int SELECTION_REASON_CUSTOM_BASE @@ -1350,73 +1355,73 @@ extends - + static int SELECTION_REASON_INITIAL
      A selection reason constant for an initial track selection.
      - + static int SELECTION_REASON_MANUAL
      A selection reason constant for an manual (i.e.
      - + static int SELECTION_REASON_TRICK_PLAY
      A selection reason constant for a trick play track selection.
      - + static int SELECTION_REASON_UNKNOWN
      A selection reason constant for selections whose reasons are unknown or unspecified.
      - + static String SERIF_NAME
      The name of the serif font family.
      - + static int SPATIALIZATION_BEHAVIOR_AUTO   - + static int SPATIALIZATION_BEHAVIOR_NEVER   - + static String SSAI_SCHEME
      The URI scheme used for content with server side ad insertion.
      - + static int STEREO_MODE_LEFT_RIGHT
      Indicates Left-Right stereo layout, used with 360/3D/VR videos.
      - + static int STEREO_MODE_MONO
      Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
      - + static int STEREO_MODE_STEREO_MESH @@ -1424,84 +1429,84 @@ extends - + static int STEREO_MODE_TOP_BOTTOM
      Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
      - + static int STREAM_TYPE_ALARM   - + static int STREAM_TYPE_DEFAULT
      The default stream type used by audio renderers.
      - + static int STREAM_TYPE_DTMF   - + static int STREAM_TYPE_MUSIC   - + static int STREAM_TYPE_NOTIFICATION   - + static int STREAM_TYPE_RING   - + static int STREAM_TYPE_SYSTEM   - + static int STREAM_TYPE_VOICE_CALL   - + static long TIME_END_OF_SOURCE
      Special constant representing a time corresponding to the end of a source.
      - + static long TIME_UNSET
      Special constant representing an unset or unknown time or duration.
      - + static int TRACK_TYPE_AUDIO
      A type constant for audio tracks.
      - + static int TRACK_TYPE_CAMERA_MOTION
      A type constant for camera motion tracks.
      - + static int TRACK_TYPE_CUSTOM_BASE @@ -1509,56 +1514,56 @@ extends - + static int TRACK_TYPE_DEFAULT
      A type constant for tracks of some default type, where the type itself is unknown.
      - + static int TRACK_TYPE_IMAGE
      A type constant for image tracks.
      - + static int TRACK_TYPE_METADATA
      A type constant for metadata tracks.
      - + static int TRACK_TYPE_NONE
      A type constant for a fake or empty track.
      - + static int TRACK_TYPE_TEXT
      A type constant for text tracks.
      - + static int TRACK_TYPE_UNKNOWN
      A type constant for tracks of unknown type.
      - + static int TRACK_TYPE_VIDEO
      A type constant for video tracks.
      - + static int TYPE_DASH @@ -1567,7 +1572,7 @@ extends - + static int TYPE_HLS @@ -1576,7 +1581,7 @@ extends - + static int TYPE_OTHER @@ -1585,7 +1590,7 @@ extends - + static int TYPE_RTSP @@ -1594,7 +1599,7 @@ extends - + static int TYPE_SS @@ -1603,87 +1608,87 @@ extends - + static int USAGE_ALARM   - + static int USAGE_ASSISTANCE_ACCESSIBILITY   - + static int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE   - + static int USAGE_ASSISTANCE_SONIFICATION   - + static int USAGE_ASSISTANT   - + static int USAGE_GAME   - + static int USAGE_MEDIA   - + static int USAGE_NOTIFICATION   - + static int USAGE_NOTIFICATION_COMMUNICATION_DELAYED   - + static int USAGE_NOTIFICATION_COMMUNICATION_INSTANT   - + static int USAGE_NOTIFICATION_COMMUNICATION_REQUEST   - + static int USAGE_NOTIFICATION_EVENT   - + static int USAGE_NOTIFICATION_RINGTONE   - + static int USAGE_UNKNOWN   - + static int USAGE_VOICE_COMMUNICATION   - + static int USAGE_VOICE_COMMUNICATION_SIGNALLING   - + static String UTF16_NAME @@ -1692,7 +1697,7 @@ extends - + static String UTF16LE_NAME @@ -1701,7 +1706,7 @@ extends - + static String UTF8_NAME @@ -1710,70 +1715,70 @@ extends - + static UUID UUID_NIL
      The Nil UUID as defined by RFC4122.
      - + static int VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF - + static int VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS
      Strategy to call Surface.setFrameRate(float, int, int) with Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS when the output frame rate is known.
      - + static int VIDEO_OUTPUT_MODE_NONE
      Video decoder output mode is not set.
      - + static int VIDEO_OUTPUT_MODE_SURFACE_YUV
      Video decoder output mode that renders 4:2:0 YUV planes directly to a surface.
      - + static int VIDEO_OUTPUT_MODE_YUV
      Video decoder output mode that outputs raw 4:2:0 YUV planes.
      - + static int VIDEO_SCALING_MODE_DEFAULT
      A default video scaling mode for MediaCodec-based renderers.
      - + static int VIDEO_SCALING_MODE_SCALE_TO_FIT - + static int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING - + static int WAKE_MODE_LOCAL @@ -1781,7 +1786,7 @@ extends - + static int WAKE_MODE_NETWORK @@ -1789,14 +1794,14 @@ extends WifiManager.WifiLock during playback. - + static int WAKE_MODE_NONE
      A wake mode that will not cause the player to hold any locks.
      - + static UUID WIDEVINE_UUID @@ -2608,6 +2613,20 @@ public static final  + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/ExoPlaybackException.html b/docs/doc/reference/com/google/android/exoplayer2/ExoPlaybackException.html index a861cfbd9b..6dae3b33f8 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ExoPlaybackException.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ExoPlaybackException.html @@ -405,7 +405,7 @@ extends PlaybackException -getErrorCodeName, getErrorCodeName, keyForField +getErrorCodeName, getErrorCodeName
      Parameters:
      @@ -1112,6 +1120,28 @@ public  + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html index 5d97c95156..538b11869b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ExoPlayer.html @@ -181,15 +181,15 @@ extends @@ -1159,7 +1165,9 @@ extends

      getClock

      Clock getClock()
      -
      Returns the Clock used for playback.
      +
      Returns the Clock used for playback. + +

      This method can be called from any thread.

      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html index f2151164a7..3f46a06d3b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Format.Builder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -402,6 +402,20 @@ extends Format.Builder +setTileCountHorizontal​(int tileCountHorizontal) + + + + + +Format.Builder +setTileCountVertical​(int tileCountVertical) + + + + + +Format.Builder setWidth​(int width) @@ -975,6 +989,40 @@ public  + + + + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/Format.html b/docs/doc/reference/com/google/android/exoplayer2/Format.html index d4ec34a903..fc4a88a526 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Format.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Format.html @@ -206,6 +206,13 @@ implements accessibilityChannel + + +

      Fields relevant to image formats

      + + @@ -497,6 +504,20 @@ implements int +tileCountHorizontal + +
      The number of horizontal tiles in an image, or NO_VALUE if not known or applicable.
      + + + +int +tileCountVertical + +
      The number of vertical tiles in an image, or NO_VALUE if not known or applicable.
      + + + +int width
      The width of the video in pixels, or NO_VALUE if unknown or not applicable.
      @@ -1193,6 +1214,26 @@ public final The Accessibility channel, or NO_VALUE if not known or applicable. + + + +
        +
      • +

        tileCountHorizontal

        +
        public final int tileCountHorizontal
        +
        The number of horizontal tiles in an image, or NO_VALUE if not known or applicable.
        +
      • +
      + + + +
        +
      • +

        tileCountVertical

        +
        public final int tileCountVertical
        +
        The number of vertical tiles in an image, or NO_VALUE if not known or applicable.
        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ForwardingPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/ForwardingPlayer.html index 44729c9e78..2f87a6f080 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ForwardingPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ForwardingPlayer.html @@ -1221,7 +1221,7 @@ implements Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      @@ -1238,7 +1238,7 @@ implements Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
      @@ -1259,7 +1259,7 @@ implements Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -1369,7 +1369,7 @@ implements Specified by:
      addMediaItems in interface Player
      Parameters:
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -1388,7 +1388,7 @@ implements Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -1405,7 +1405,8 @@ implements Specified by:
      moveMediaItem in interface Player
      Parameters:
      -
      currentIndex - The current index of the media item to move.
      +
      currentIndex - The current index of the media item to move. If the index is larger than + the size of the playlist, the request is ignored.
      newIndex - The new index of the media item. If the new index is larger than the size of the playlist the item is moved to the end of the playlist.
      @@ -1425,8 +1426,10 @@ implements Specified by:
      moveMediaItems in interface Player
      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -1445,7 +1448,8 @@ implements Specified by:
      removeMediaItem in interface Player
      Parameters:
      -
      index - The index at which to remove the media item.
      +
      index - The index at which to remove the media item. If the index is larger than the size + of the playlist, the request is ignored.
      @@ -1462,9 +1466,10 @@ implements Specified by:
      removeMediaItems in interface Player
      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -1561,7 +1566,7 @@ implements Specified by:
      getPlaybackState in interface Player
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -1579,7 +1584,7 @@ implements Specified by:
      getPlaybackSuppressionReason in interface Player
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -1796,7 +1801,8 @@ public seekToDefaultPosition in interface Player
      Parameters:
      mediaItemIndex - The index of the MediaItem whose associated default position - should be seeked to.
      + should be seeked to. If the index is larger than the size of the playlist, the request is + ignored.
      @@ -1830,7 +1836,8 @@ public Specified by:
      seekTo in interface Player
      Parameters:
      -
      mediaItemIndex - The index of the MediaItem.
      +
      mediaItemIndex - The index of the MediaItem. If the index is larger than the size + of the playlist, the request is ignored.
      positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET to seek to the media item's default position.
      @@ -2175,7 +2182,7 @@ public void seekToNextWindow()
      setPlaybackSpeed in interface Player
      Parameters:
      speed - The linear factor by which playback will be sped up. Must be higher than 0. 1 is - normal speed, 2 is twice as fast, 0.5 is half normal speed...
      + normal speed, 2 is twice as fast, 0.5 is half normal speed.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/LegacyMediaPlayerWrapper.html b/docs/doc/reference/com/google/android/exoplayer2/LegacyMediaPlayerWrapper.html index 97861af301..762054e1d6 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/LegacyMediaPlayerWrapper.html +++ b/docs/doc/reference/com/google/android/exoplayer2/LegacyMediaPlayerWrapper.html @@ -164,7 +164,7 @@ extends SimpleBasePlayer -SimpleBasePlayer.State +SimpleBasePlayer.MediaItemData, SimpleBasePlayer.PeriodData, SimpleBasePlayer.PositionSupplier, SimpleBasePlayer.State diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html index 24decbb470..ac8f3b8e0d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.ClippingConfiguration.Builder.html @@ -158,7 +158,7 @@ extends Builder() -
      Constructs an instance.
      +
      Creates a new instance with default values.
      @@ -266,7 +266,7 @@ extends

      Builder

      public Builder()
      -
      Constructs an instance.
      +
      Creates a new instance with default values.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.LiveConfiguration.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.LiveConfiguration.Builder.html index b57ad07362..8c41325714 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaItem.LiveConfiguration.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaItem.LiveConfiguration.Builder.html @@ -158,7 +158,7 @@ extends
      Builder() -
      Constructs an instance.
      +
      Creates a new instance with default values.
      @@ -252,7 +252,7 @@ extends

      Builder

      public Builder()
      -
      Constructs an instance.
      +
      Creates a new instance with default values.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html index 7ab03cbc38..eeb1ba465b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.Builder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":42,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":42}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":42,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":42}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -324,117 +324,131 @@ extends
      MediaMetadata.Builder +setIsBrowsable​(Boolean isBrowsable) + +
      Sets whether the media is a browsable folder.
      + + + +MediaMetadata.Builder setIsPlayable​(Boolean isPlayable)
      Sets whether the media is playable.
      - + +MediaMetadata.Builder +setMediaType​(@MediaType Integer mediaType) + + + + + MediaMetadata.Builder setOverallRating​(Rating overallRating)
      Sets the overall Rating.
      - + MediaMetadata.Builder setRecordingDay​(Integer recordingDay)
      Sets the day of the recording date.
      - + MediaMetadata.Builder setRecordingMonth​(Integer recordingMonth)
      Sets the month of the recording date.
      - + MediaMetadata.Builder setRecordingYear​(Integer recordingYear)
      Sets the year of the recording date.
      - + MediaMetadata.Builder setReleaseDay​(Integer releaseDay)
      Sets the day of the release date.
      - + MediaMetadata.Builder setReleaseMonth​(Integer releaseMonth)
      Sets the month of the release date.
      - + MediaMetadata.Builder setReleaseYear​(Integer releaseYear)
      Sets the year of the release date.
      - + MediaMetadata.Builder setStation​(CharSequence station)
      Sets the name of the station streaming the media.
      - + MediaMetadata.Builder setSubtitle​(CharSequence subtitle)
      Sets the subtitle.
      - + MediaMetadata.Builder setTitle​(CharSequence title)
      Sets the title.
      - + MediaMetadata.Builder setTotalDiscCount​(Integer totalDiscCount)
      Sets the total number of discs.
      - + MediaMetadata.Builder setTotalTrackCount​(Integer totalTrackCount)
      Sets the total number of tracks.
      - + MediaMetadata.Builder setTrackNumber​(Integer trackNumber)
      Sets the track number.
      - + MediaMetadata.Builder setUserRating​(Rating userRating)
      Sets the user Rating.
      - + MediaMetadata.Builder setWriter​(CharSequence writer)
      Sets the writer.
      - + MediaMetadata.Builder setYear​(Integer year) @@ -687,7 +701,22 @@ public @CanIgnoreReturnValue public MediaMetadata.Builder setFolderType​(@Nullable @FolderType @FolderType Integer folderType) - +
      Sets the MediaMetadata.FolderType. + +

      This method will be deprecated. Use setIsBrowsable(java.lang.Boolean) to indicate if an item is a + browsable folder and use setMediaType(java.lang.Integer) to indicate the type of the folder.

      + + + + + +
        +
      • +

        setIsBrowsable

        +
        @CanIgnoreReturnValue
        +public MediaMetadata.Builder setIsBrowsable​(@Nullable
        +                                            Boolean isBrowsable)
        +
        Sets whether the media is a browsable folder.
      @@ -893,6 +922,18 @@ public Sets the name of the station streaming the media. + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html index c555f2c606..c46d19187d 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.FolderType.html @@ -120,7 +120,10 @@ public static @interface MediaMetadata.FolderType<
      The folder type of the media item.

      This can be used as the type of a browsable bluetooth folder (see section 6.10.2.2 of the Bluetooth - AVRCP 1.6.2).

      + AVRCP 1.6.2). + +

      One of MediaMetadata.FOLDER_TYPE_NONE, MediaMetadata.FOLDER_TYPE_MIXED, MediaMetadata.FOLDER_TYPE_TITLES, + MediaMetadata.FOLDER_TYPE_ALBUMS, MediaMetadata.FOLDER_TYPE_ARTISTS, MediaMetadata.FOLDER_TYPE_GENRES, MediaMetadata.FOLDER_TYPE_PLAYLISTS or MediaMetadata.FOLDER_TYPE_YEARS. diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html index 06dab22929..c1f86afb1b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.PictureType.html @@ -120,7 +120,11 @@ public static @interface MediaMetadata.PictureType

      The picture type of the artwork.

      Values sourced from the ID3 v2.4 specification (See section 4.14 of - https://id3.org/id3v2.4.0-frames).

      + https://id3.org/id3v2.4.0-frames). + +

      One of MediaMetadata.PICTURE_TYPE_OTHER, MediaMetadata.PICTURE_TYPE_FILE_ICON, MediaMetadata.PICTURE_TYPE_FILE_ICON_OTHER, MediaMetadata.PICTURE_TYPE_FRONT_COVER, MediaMetadata.PICTURE_TYPE_BACK_COVER, MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE, MediaMetadata.PICTURE_TYPE_MEDIA, + MediaMetadata.PICTURE_TYPE_LEAD_ARTIST_PERFORMER, MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, MediaMetadata.PICTURE_TYPE_CONDUCTOR, MediaMetadata.PICTURE_TYPE_BAND_ORCHESTRA, MediaMetadata.PICTURE_TYPE_COMPOSER, + MediaMetadata.PICTURE_TYPE_LYRICIST, MediaMetadata.PICTURE_TYPE_RECORDING_LOCATION, MediaMetadata.PICTURE_TYPE_DURING_RECORDING, MediaMetadata.PICTURE_TYPE_DURING_PERFORMANCE, MediaMetadata.PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, MediaMetadata.PICTURE_TYPE_A_BRIGHT_COLORED_FISH, MediaMetadata.PICTURE_TYPE_ILLUSTRATION, MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO or MediaMetadata.PICTURE_TYPE_PUBLISHER_STUDIO_LOGO. diff --git a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html index 4b7789a4a9..f9204bb3bd 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html +++ b/docs/doc/reference/com/google/android/exoplayer2/MediaMetadata.html @@ -173,6 +173,13 @@ implements static interface  +MediaMetadata.MediaType + +

      The type of content described by the media item.
      + + + +static interface  MediaMetadata.PictureType
      The picture type of the artwork.
      @@ -380,9 +387,281 @@ implements Boolean +isBrowsable + +
      Optional boolean to indicate that the media is a browsable folder.
      + + + +Boolean isPlayable -
      Optional boolean for media playability.
      +
      Optional boolean to indicate that the media is playable.
      + + + +static int +MEDIA_TYPE_ALBUM + +
      MediaMetadata.MediaType for a group of items (e.g., music) belonging to an + album.
      + + + +static int +MEDIA_TYPE_ARTIST + +
      MediaMetadata.MediaType for a group of items (e.g., music) from the same + artist.
      + + + +static int +MEDIA_TYPE_AUDIO_BOOK + +
      MediaMetadata.MediaType for a group of items forming an audio book.
      + + + +static int +MEDIA_TYPE_AUDIO_BOOK_CHAPTER + +
      MediaMetadata.MediaType for an audio book chapter.
      + + + +static int +MEDIA_TYPE_FOLDER_ALBUMS + +
      MediaMetadata.MediaType for a folder containing albums.
      + + + +static int +MEDIA_TYPE_FOLDER_ARTISTS + +
      MediaMetadata.MediaType for a folder containing artists.
      + + + +static int +MEDIA_TYPE_FOLDER_AUDIO_BOOKS + +
      MediaMetadata.MediaType for a folder containing audio books.
      + + + +static int +MEDIA_TYPE_FOLDER_GENRES + +
      MediaMetadata.MediaType for a folder containing genres.
      + + + +static int +MEDIA_TYPE_FOLDER_MIXED + +
      MediaMetadata.MediaType for a folder with mixed or undetermined content.
      + + + +static int +MEDIA_TYPE_FOLDER_MOVIES + +
      MediaMetadata.MediaType for a folder containing movies.
      + + + +static int +MEDIA_TYPE_FOLDER_NEWS + +
      MediaMetadata.MediaType for a folder containing news.
      + + + +static int +MEDIA_TYPE_FOLDER_PLAYLISTS + +
      MediaMetadata.MediaType for a folder containing playlists.
      + + + +static int +MEDIA_TYPE_FOLDER_PODCASTS + +
      MediaMetadata.MediaType for a folder containing podcasts.
      + + + +static int +MEDIA_TYPE_FOLDER_RADIO_STATIONS + +
      MediaMetadata.MediaType for a folder containing radio + stations.
      + + + +static int +MEDIA_TYPE_FOLDER_TRAILERS + +
      MediaMetadata.MediaType for a folder containing movie trailers.
      + + + +static int +MEDIA_TYPE_FOLDER_TV_CHANNELS + +
      MediaMetadata.MediaType for a folder containing TV channels.
      + + + +static int +MEDIA_TYPE_FOLDER_TV_SERIES + +
      MediaMetadata.MediaType for a folder containing TV series.
      + + + +static int +MEDIA_TYPE_FOLDER_TV_SHOWS + +
      MediaMetadata.MediaType for a folder containing TV shows.
      + + + +static int +MEDIA_TYPE_FOLDER_VIDEOS + +
      MediaMetadata.MediaType for a folder containing videos.
      + + + +static int +MEDIA_TYPE_FOLDER_YEARS + +
      MediaMetadata.MediaType for a folder containing years.
      + + + +static int +MEDIA_TYPE_GENRE + +
      MediaMetadata.MediaType for a group of items (e.g., music) of the same + genre.
      + + + +static int +MEDIA_TYPE_MIXED + +
      Media of undetermined type or a mix of multiple media types.
      + + + +static int +MEDIA_TYPE_MOVIE + + + + + +static int +MEDIA_TYPE_MUSIC + + + + + +static int +MEDIA_TYPE_NEWS + + + + + +static int +MEDIA_TYPE_PLAYLIST + +
      MediaMetadata.MediaType for a group of items (e.g., music) forming a + playlist.
      + + + +static int +MEDIA_TYPE_PODCAST + +
      MediaMetadata.MediaType for a group of items belonging to a podcast.
      + + + +static int +MEDIA_TYPE_PODCAST_EPISODE + +
      MediaMetadata.MediaType for a podcast episode.
      + + + +static int +MEDIA_TYPE_RADIO_STATION + +
      MediaMetadata.MediaType for a radio station.
      + + + +static int +MEDIA_TYPE_TRAILER + +
      MediaMetadata.MediaType for a movie trailer.
      + + + +static int +MEDIA_TYPE_TV_CHANNEL + +
      MediaMetadata.MediaType for a group of items that are part of a TV channel.
      + + + +static int +MEDIA_TYPE_TV_SEASON + +
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      + + + +static int +MEDIA_TYPE_TV_SERIES + +
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      + + + +static int +MEDIA_TYPE_TV_SHOW + + + + + +static int +MEDIA_TYPE_VIDEO + + + + + +static int +MEDIA_TYPE_YEAR + +
      MediaMetadata.MediaType for a group of items (e.g., music) from the same + year.
      + + + +@MediaType Integer +mediaType + + @@ -670,6 +949,521 @@ implements + + +
        +
      • +

        MEDIA_TYPE_MIXED

        +
        public static final int MEDIA_TYPE_MIXED
        +
        Media of undetermined type or a mix of multiple media types.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1231,7 +2025,21 @@ public final @FolderType public final @FolderType Integer folderType - +
      Optional MediaMetadata.FolderType. + +

      This field will be deprecated. Use isBrowsable to indicate if an item is a + browsable folder and use mediaType to indicate the type of the folder.

      + + + + + +
        +
      • +

        isBrowsable

        +
        @Nullable
        +public final Boolean isBrowsable
        +
        Optional boolean to indicate that the media is a browsable folder.
      @@ -1242,7 +2050,7 @@ public final Boolean isPlayable -
      Optional boolean for media playability.
      +
      Optional boolean to indicate that the media is playable.
      @@ -1421,6 +2229,17 @@ public final Optional name of the station streaming the media. + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/PlaybackException.html b/docs/doc/reference/com/google/android/exoplayer2/PlaybackException.html index 7b1b28921d..29a823975f 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/PlaybackException.html +++ b/docs/doc/reference/com/google/android/exoplayer2/PlaybackException.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":9,"i3":9,"i4":10}; +var data = {"i0":10,"i1":10,"i2":9,"i3":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -570,14 +570,6 @@ implements -protected static String -keyForField​(int field) - -
      Converts the given field number to a string which can be used as a field key when implementing - toBundle() and Bundleable.Creator.
      - - - Bundle toBundle() @@ -1166,7 +1158,7 @@ public final @com.google.android.exoplayer2.PlaybackException.ErrorCode int and Bundleable.Creator.

      Subclasses should obtain their Bundle's field keys by applying a non-negative - offset on this constant and passing the result to keyForField(int). + offset on this constant and passing the result to Util.intToStringMaxRadix(int).

      See Also:
      Constant Field Values
      @@ -1289,7 +1281,7 @@ public boolean errorInfoEquals​(@Nullable - diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html index 95785e88e1..926399528c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.Builder.html @@ -190,7 +190,7 @@ extends Player.Commands.Builder addAll​(@com.google.android.exoplayer2.Player.Command int... commands) -
      Adds commands.
      +
      Adds commands.
      @@ -204,7 +204,7 @@ extends Player.Commands.Builder addAllCommands() -
      Adds all existing commands.
      +
      Adds all existing commands.
      @@ -233,7 +233,7 @@ extends Player.Commands.Builder removeAll​(@com.google.android.exoplayer2.Player.Command int... commands) -
      Removes commands.
      +
      Removes commands.
      @@ -339,10 +339,10 @@ public @CanIgnoreReturnValue public Player.Commands.Builder addAll​(@Command @com.google.android.exoplayer2.Player.Command int... commands) -
      Adds commands.
      +
      Adds commands.
      Parameters:
      -
      commands - The commands to add.
      +
      commands - The commands to add.
      Returns:
      This builder.
      Throws:
      @@ -361,7 +361,7 @@ public Adds Player.Commands.
      Parameters:
      -
      commands - The set of commands to add.
      +
      commands - The set of commands to add.
      Returns:
      This builder.
      Throws:
      @@ -377,7 +377,7 @@ public @CanIgnoreReturnValue public Player.Commands.Builder addAllCommands() -
      Adds all existing commands.
      +
      Adds all existing commands.
      Returns:
      This builder.
      @@ -437,10 +437,10 @@ public @CanIgnoreReturnValue public Player.Commands.Builder removeAll​(@Command @com.google.android.exoplayer2.Player.Command int... commands) -
      Removes commands.
      +
      Removes commands.
      Parameters:
      -
      commands - The commands to remove.
      +
      commands - The commands to remove.
      Returns:
      This builder.
      Throws:
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html index 4a740da08a..e7bd996928 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Commands.html @@ -140,7 +140,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      public static final class Player.Commands
       extends Object
       implements Bundleable
      -
      A set of commands. +
      A set of commands.

      Instances are immutable.

      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Events.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Events.html index cbca9d7e72..4bd5727082 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Events.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Events.html @@ -135,7 +135,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      public static final class Player.Events
       extends Object
      -
      A set of events.
      +
      A set of events.
      @@ -190,7 +190,7 @@ extends boolean containsAny​(@com.google.android.exoplayer2.Player.Event int... events) -
      Returns whether any of the given events occurred.
      +
      Returns whether any of the given events occurred.
      @@ -251,7 +251,7 @@ extends Creates an instance.
      Parameters:
      -
      flags - The FlagSet containing the events.
      +
      flags - The FlagSet containing the events.
      @@ -290,12 +290,12 @@ extends containsAny
      public boolean containsAny​(@Event
                                  @com.google.android.exoplayer2.Player.Event int... events)
      -
      Returns whether any of the given events occurred.
      +
      Returns whether any of the given events occurred.
      Parameters:
      -
      events - The events.
      +
      events - The events.
      Returns:
      -
      Whether any of the events occurred.
      +
      Whether any of the events occurred.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.Listener.html b/docs/doc/reference/com/google/android/exoplayer2/Player.Listener.html index d0471f4c52..a42186549a 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.Listener.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.Listener.html @@ -134,9 +134,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

      public static interface Player.Listener
      -
      Listener of all changes in the Player. +
      Listener for changes in a Player. -

      All methods have no-op default implementations to allow selective overrides.

      +

      All methods have no-op default implementations to allow selective overrides. + +

      If the return value of a Player getter changes due to a change in command availability, the corresponding listener + method(s) will be invoked. If the return value of a Player getter does not change + because the corresponding command is not + available, the corresponding listener method will not be invoked.

      @@ -161,7 +166,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onAudioAttributesChanged​(AudioAttributes audioAttributes) -
      Called when the audio attributes change.
      +
      Called when the value of Player.getAudioAttributes() changes.
      @@ -183,7 +188,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onCues​(CueGroup cueGroup) -
      Called when there is a change in the CueGroup.
      +
      Called when the value of Player.getCurrentCues() changes.
      @@ -207,7 +212,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); onDeviceVolumeChanged​(int volume, boolean muted) -
      Called when the device volume or mute state changes.
      +
      Called when the value of Player.getDeviceVolume() or Player.isDeviceMuted() changes.
      @@ -261,7 +266,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onMediaMetadataChanged​(MediaMetadata mediaMetadata) -
      Called when the combined MediaMetadata changes.
      +
      Called when the value of Player.getMediaMetadata() changes.
      @@ -275,7 +280,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onPlaybackParametersChanged​(PlaybackParameters playbackParameters) -
      Called when the current playback parameters change.
      +
      Called when the value of Player.getPlaybackParameters() changes.
      @@ -320,7 +325,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onPlaylistMetadataChanged​(MediaMetadata mediaMetadata) -
      Called when the playlist MediaMetadata changes.
      +
      Called when the value of Player.getPlaylistMetadata() changes.
      @@ -415,14 +420,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); onTimelineChanged​(Timeline timeline, @com.google.android.exoplayer2.Player.TimelineChangeReason int reason) -
      Called when the timeline has been refreshed.
      +
      Called when the value of Player.getCurrentTimeline() changes.
      default void onTracksChanged​(Tracks tracks) -
      Called when the tracks change.
      +
      Called when the value of Player.getCurrentTracks() changes.
      @@ -443,7 +448,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); default void onVolumeChanged​(float volume) -
      Called when the volume changes.
      +
      Called when the value of Player.getVolume() changes.
      @@ -476,8 +481,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

      State changes and events that happen within one Looper message queue iteration are reported together and only after all individual callbacks were triggered. -

      Only state changes represented by events are reported through this method. -

      Listeners should prefer this method over individual callbacks in the following cases:

        @@ -510,7 +513,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
        default void onTimelineChanged​(Timeline timeline,
                                        @TimelineChangeReason
                                        @com.google.android.exoplayer2.Player.TimelineChangeReason int reason)
        -
        Called when the timeline has been refreshed. +
        Called when the value of Player.getCurrentTimeline() changes.

        Note that the current MediaItem or playback position may change as a result of a timeline change. If playback can't continue smoothly because of this timeline change, a @@ -539,8 +542,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

        Called when playback transitions to a media item or starts repeating a media item according to the current repeat mode. -

        Note that this callback is also called when the playlist becomes non-empty or empty as a - consequence of a playlist change. +

        Note that this callback is also called when the value of Player.getCurrentTimeline() + becomes non-empty or empty.

        onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

        @@ -558,7 +561,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      • onTracksChanged

        default void onTracksChanged​(Tracks tracks)
        -
        Called when the tracks change. +
        Called when the value of Player.getCurrentTracks() changes.

        onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

        @@ -575,11 +578,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      • onMediaMetadataChanged

        default void onMediaMetadataChanged​(MediaMetadata mediaMetadata)
        -
        Called when the combined MediaMetadata changes. - -

        The provided MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and - any timed metadata that has been parsed from the media and output via onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, it will be prioritised above the same field coming from static or - timed metadata. +

        Called when the value of Player.getMediaMetadata() changes.

        This method may be called multiple times in quick succession. @@ -598,7 +597,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

      • onPlaylistMetadataChanged

        default void onPlaylistMetadataChanged​(MediaMetadata mediaMetadata)
        -
        Called when the playlist MediaMetadata changes. +
        Called when the value of Player.getPlaylistMetadata() changes.

        onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

        @@ -698,7 +697,7 @@ default void onPlayerStateChanged​(boolean playWhenReady, other events that happen in the same Looper message queue iteration.
        Parameters:
        -
        playbackState - The new playback state.
        +
        playbackState - The new playback Player.State.
      @@ -718,7 +717,7 @@ default void onPlayerStateChanged​(boolean playWhenReady,
      Parameters:
      playWhenReady - Whether playback will proceed when ready.
      -
      reason - The reason for the change.
      +
      reason - The Player.PlayWhenReadyChangeReason for the change.
      @@ -788,7 +787,7 @@ default void onPlayerStateChanged​(boolean playWhenReady, other events that happen in the same Looper message queue iteration.
      Parameters:
      -
      shuffleModeEnabled - Whether shuffling of media items is enabled.
      +
      shuffleModeEnabled - Whether shuffling of media items is enabled.
      @@ -881,10 +880,10 @@ default void onPositionDiscontinuity​(

      onPlaybackParametersChanged

      default void onPlaybackParametersChanged​(PlaybackParameters playbackParameters)
      -
      Called when the current playback parameters change. The playback parameters may change due to - a call to Player.setPlaybackParameters(PlaybackParameters), or the player itself may change - them (for example, if audio playback switches to passthrough or offload mode, where speed - adjustment is no longer possible). +
      Called when the value of Player.getPlaybackParameters() changes. The playback parameters + may change due to a call to Player.setPlaybackParameters(PlaybackParameters), or the player + itself may change them (for example, if audio playback switches to passthrough or offload + mode, where speed adjustment is no longer possible).

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -983,7 +982,7 @@ default void onSeekProcessed()
    • onAudioAttributesChanged

      default void onAudioAttributesChanged​(AudioAttributes audioAttributes)
      -
      Called when the audio attributes change. +
      Called when the value of Player.getAudioAttributes() changes.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -1000,7 +999,7 @@ default void onSeekProcessed()
    • onVolumeChanged

      default void onVolumeChanged​(float volume)
      -
      Called when the volume changes. +
      Called when the value of Player.getVolume() changes.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -1052,7 +1051,7 @@ default void onSeekProcessed()

      onDeviceVolumeChanged

      default void onDeviceVolumeChanged​(int volume,
                                          boolean muted)
      -
      Called when the device volume or mute state changes. +
      Called when the value of Player.getDeviceVolume() or Player.isDeviceMuted() changes.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -1127,10 +1126,10 @@ default void onCues​(Deprecated.
      -
      Called when there is a change in the Cues. +
      Called when the value of Player.getCurrentCues() changes. -

      Both onCues(List) and onCues(CueGroup) are called when there is a change - in the cues. You should only implement one or the other. +

      Both this method and onCues(CueGroup) are called when there is a change in the + cues. You should only implement one or the other.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -1143,10 +1142,10 @@ default void onCues​(

      onCues

      default void onCues​(CueGroup cueGroup)
      -
      Called when there is a change in the CueGroup. +
      Called when the value of Player.getCurrentCues() changes. -

      Both onCues(List) and onCues(CueGroup) are called when there is a change - in the cues. You should only implement one or the other. +

      Both this method and onCues(List) are called when there is a change in the cues. + You should only implement one or the other.

      onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html b/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html index 14ab0b9589..52221c6526 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.PositionInfo.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -339,6 +339,15 @@ implements Returns a Bundle representing the information stored in this object.
      + +Bundle +toBundle​(boolean canAccessCurrentMediaItem, + boolean canAccessTimeline) + +
      Returns a Bundle representing the information stored in this object, filtered by + available commands.
      + +
    • + + + +
        +
      • +

        toBundle

        +
        public Bundle toBundle​(boolean canAccessCurrentMediaItem,
        +                       boolean canAccessTimeline)
        +
        Returns a Bundle representing the information stored in this object, filtered by + available commands.
        +
        +
        Parameters:
        +
        canAccessCurrentMediaItem - Whether the Bundle should contain information + accessbile with Player.COMMAND_GET_CURRENT_MEDIA_ITEM.
        +
        canAccessTimeline - Whether the Bundle should contain information accessbile + with Player.COMMAND_GET_TIMELINE.
        +
        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Player.html b/docs/doc/reference/com/google/android/exoplayer2/Player.html index eaca85fcb0..3e52a33c9a 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Player.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Player.html @@ -133,6 +133,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A media player interface defining traditional high-level functionality, such as the ability to play, pause, seek and query properties of the currently playing media. +

      All methods must be called from a single application + thread unless indicated otherwise. Callbacks in registered listeners are called on the same + thread. +

      This interface includes some convenience methods that can be implemented by calling other methods in the interface. BasePlayer implements these convenience methods so inheriting BasePlayer is recommended when implementing the interface so that only the minimal set of @@ -170,14 +174,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static interface  Player.Command -

      Commands that can be executed on a Player.
      +
      Commands that indicate which method calls are currently permitted on a particular + Player instance.
      static class  Player.Commands -
      A set of commands.
      +
      A set of commands.
      @@ -198,14 +203,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static class  Player.Events -
      A set of events.
      +
      A set of events.
      static interface  Player.Listener -
      Listener of all changes in the Player.
      +
      Listener for changes in a Player.
      @@ -286,7 +291,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_CHANGE_MEDIA_ITEMS -
      Command to change the MediaItems in the playlist.
      +
      Command to change the media items in the playlist.
      @@ -300,7 +305,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_GET_CURRENT_MEDIA_ITEM -
      Command to get the currently playing MediaItem.
      +
      Command to get information about the currently playing MediaItem.
      @@ -314,7 +319,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_GET_MEDIA_ITEMS_METADATA -
      Command to get the MediaItems metadata.
      +
      Command to get metadata related to the playlist and current MediaItem.
      @@ -370,21 +375,21 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_SEEK_BACK -
      Command to seek back by a fixed increment into the current MediaItem.
      +
      Command to seek back by a fixed increment inside the current MediaItem.
      static int COMMAND_SEEK_FORWARD -
      Command to seek forward by a fixed increment into the current MediaItem.
      +
      Command to seek forward by a fixed increment inside the current MediaItem.
      static int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM -
      Command to seek anywhere into the current MediaItem.
      +
      Command to seek anywhere inside the current MediaItem.
      @@ -414,7 +419,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_SEEK_TO_NEXT -
      Command to seek to a later position in the current or next MediaItem.
      +
      Command to seek to a later position in the current MediaItem or the default position of + the next MediaItem.
      @@ -437,7 +443,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_SEEK_TO_PREVIOUS -
      Command to seek to an earlier position in the current or previous MediaItem.
      +
      Command to seek to an earlier position in the current MediaItem or the default position + of the previous MediaItem.
      @@ -469,7 +476,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_SET_DEVICE_VOLUME -
      Command to set the device volume and mute it.
      +
      Command to set the device volume.
      @@ -483,7 +490,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_SET_MEDIA_ITEMS_METADATA -
      Command to set the MediaItems metadata.
      +
      Command to set the playlist metadata.
      @@ -532,7 +539,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int COMMAND_STOP -
      Command to stop playback or release the player.
      +
      Command to stop playback.
      @@ -1243,7 +1250,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); int getMediaItemCount() -
      Returns the number of media items in the playlist.
      +
      Returns the number of media items in the playlist.
      @@ -1282,7 +1289,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); @com.google.android.exoplayer2.Player.State int getPlaybackState() -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      @@ -1757,8 +1764,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); void setMediaItems​(List<MediaItem> mediaItems) -
      Clears the playlist, adds the specified MediaItems and resets the position to - the default position.
      +
      Clears the playlist, adds the specified media items and resets the + position to the default position.
      @@ -1766,7 +1773,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      @@ -1775,7 +1782,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); int startIndex, long startPositionMs) -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      @@ -2729,7 +2736,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • COMMAND_PLAY_PAUSE

      static final int COMMAND_PLAY_PAUSE
      -
      Command to start, pause or resume playback.
      +
      Command to start, pause or resume playback. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -2743,7 +2758,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • COMMAND_PREPARE

      static final int COMMAND_PREPARE
      -
      Command to prepare the player.
      +
      Command to prepare the player. + +

      The prepare() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2757,7 +2774,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • COMMAND_STOP

      static final int COMMAND_STOP
      -
      Command to stop playback or release the player.
      +
      Command to stop playback. + +

      The stop() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2771,7 +2790,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • COMMAND_SEEK_TO_DEFAULT_POSITION

      static final int COMMAND_SEEK_TO_DEFAULT_POSITION
      -
      Command to seek to the default position of the current MediaItem.
      +
      Command to seek to the default position of the current MediaItem. + +

      The seekToDefaultPosition() method must only be called if this command is + available.

      See Also:
      Constant Field Values
      @@ -2785,7 +2807,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM

      static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM
      -
      Command to seek anywhere into the current MediaItem.
      +
      Command to seek anywhere inside the current MediaItem. + +

      The seekTo(long) method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2816,7 +2840,10 @@ static final int COMMAND_SEEK_IN_CURRENT_WINDOW
    • COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM

      static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
      -
      Command to seek to the default position of the previous MediaItem.
      +
      Command to seek to the default position of the previous MediaItem. + +

      The seekToPreviousMediaItem() method must only be called if this command is + available.

      See Also:
      Constant Field Values
      @@ -2847,7 +2874,10 @@ static final int COMMAND_SEEK_TO_PREVIOUS_WINDOW
    • COMMAND_SEEK_TO_PREVIOUS

      static final int COMMAND_SEEK_TO_PREVIOUS
      -
      Command to seek to an earlier position in the current or previous MediaItem.
      +
      Command to seek to an earlier position in the current MediaItem or the default position + of the previous MediaItem. + +

      The seekToPrevious() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2861,7 +2891,9 @@ static final int COMMAND_SEEK_TO_PREVIOUS_WINDOW
    • COMMAND_SEEK_TO_NEXT_MEDIA_ITEM

      static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
      -
      Command to seek to the default position of the next MediaItem.
      +
      Command to seek to the default position of the next MediaItem. + +

      The seekToNextMediaItem() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2892,7 +2924,10 @@ static final int COMMAND_SEEK_TO_NEXT_WINDOW
    • COMMAND_SEEK_TO_NEXT

      static final int COMMAND_SEEK_TO_NEXT
      -
      Command to seek to a later position in the current or next MediaItem.
      +
      Command to seek to a later position in the current MediaItem or the default position of + the next MediaItem. + +

      The seekToNext() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2906,7 +2941,14 @@ static final int COMMAND_SEEK_TO_NEXT_WINDOW
    • COMMAND_SEEK_TO_MEDIA_ITEM

      static final int COMMAND_SEEK_TO_MEDIA_ITEM
      -
      Command to seek anywhere in any MediaItem.
      +
      Command to seek anywhere in any MediaItem. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -2937,7 +2979,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SEEK_BACK

      static final int COMMAND_SEEK_BACK
      -
      Command to seek back by a fixed increment into the current MediaItem.
      +
      Command to seek back by a fixed increment inside the current MediaItem. + +

      The seekBack() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2951,7 +2995,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SEEK_FORWARD

      static final int COMMAND_SEEK_FORWARD
      -
      Command to seek forward by a fixed increment into the current MediaItem.
      +
      Command to seek forward by a fixed increment inside the current MediaItem. + +

      The seekForward() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -2965,7 +3011,14 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_SPEED_AND_PITCH

      static final int COMMAND_SET_SPEED_AND_PITCH
      -
      Command to set the playback speed and pitch.
      +
      Command to set the playback speed and pitch. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -2979,7 +3032,10 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_SHUFFLE_MODE

      static final int COMMAND_SET_SHUFFLE_MODE
      -
      Command to enable shuffling.
      +
      Command to enable shuffling. + +

      The setShuffleModeEnabled(boolean) method must only be called if this command is + available.

      See Also:
      Constant Field Values
      @@ -2993,7 +3049,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_REPEAT_MODE

      static final int COMMAND_SET_REPEAT_MODE
      -
      Command to set the repeat mode.
      +
      Command to set the repeat mode. + +

      The setRepeatMode(int) method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3007,7 +3065,27 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_CURRENT_MEDIA_ITEM

      static final int COMMAND_GET_CURRENT_MEDIA_ITEM
      -
      Command to get the currently playing MediaItem.
      +
      See Also:
      Constant Field Values
      @@ -3021,7 +3099,21 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_TIMELINE

      static final int COMMAND_GET_TIMELINE
      -
      Command to get the information about the current timeline.
      +
      Command to get the information about the current timeline. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3035,7 +3127,14 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_MEDIA_ITEMS_METADATA

      static final int COMMAND_GET_MEDIA_ITEMS_METADATA
      -
      Command to get the MediaItems metadata.
      +
      Command to get metadata related to the playlist and current MediaItem. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3049,7 +3148,10 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_MEDIA_ITEMS_METADATA

      static final int COMMAND_SET_MEDIA_ITEMS_METADATA
      -
      Command to set the MediaItems metadata.
      +
      Command to set the playlist metadata. + +

      The setPlaylistMetadata(MediaMetadata) method must only be called if this command + is available.

      See Also:
      Constant Field Values
      @@ -3063,7 +3165,15 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_MEDIA_ITEM

      static final int COMMAND_SET_MEDIA_ITEM
      -
      Command to set a MediaItem.
      +
      Command to set a MediaItem. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3077,7 +3187,24 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_CHANGE_MEDIA_ITEMS

      static final int COMMAND_CHANGE_MEDIA_ITEMS
      -
      Command to change the MediaItems in the playlist.
      +
      See Also:
      Constant Field Values
      @@ -3091,7 +3218,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_AUDIO_ATTRIBUTES

      static final int COMMAND_GET_AUDIO_ATTRIBUTES
      -
      Command to get the player current AudioAttributes.
      +
      Command to get the player current AudioAttributes. + +

      The getAudioAttributes() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3105,7 +3234,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_VOLUME

      static final int COMMAND_GET_VOLUME
      -
      Command to get the player volume.
      +
      Command to get the player volume. + +

      The getVolume() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3119,7 +3250,14 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_DEVICE_VOLUME

      static final int COMMAND_GET_DEVICE_VOLUME
      -
      Command to get the device volume and whether it is muted.
      +
      Command to get the device volume and whether it is muted. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3133,7 +3271,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_VOLUME

      static final int COMMAND_SET_VOLUME
      -
      Command to set the player volume.
      +
      Command to set the player volume. + +

      The setVolume(float) method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3147,7 +3287,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_DEVICE_VOLUME

      static final int COMMAND_SET_DEVICE_VOLUME
      -
      Command to set the device volume and mute it.
      +
      Command to set the device volume. + +

      The setDeviceVolume(int) method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3161,7 +3303,15 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_ADJUST_DEVICE_VOLUME

      static final int COMMAND_ADJUST_DEVICE_VOLUME
      -
      Command to increase and decrease the device volume and mute it.
      +
      Command to increase and decrease the device volume and mute it. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3175,7 +3325,19 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_VIDEO_SURFACE

      static final int COMMAND_SET_VIDEO_SURFACE
      -
      Command to set and clear the surface on which to render the video.
      +
      Command to set and clear the surface on which to render the video. + +

      The following methods must only be called if this command is available: + +

      See Also:
      Constant Field Values
      @@ -3189,7 +3351,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_TEXT

      static final int COMMAND_GET_TEXT
      -
      Command to get the text that should currently be displayed by the player.
      +
      Command to get the text that should currently be displayed by the player. + +

      The getCurrentCues() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3203,7 +3367,10 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_SET_TRACK_SELECTION_PARAMETERS

      static final int COMMAND_SET_TRACK_SELECTION_PARAMETERS
      -
      Command to set the player's track selection parameters.
      +
      Command to set the player's track selection parameters. + +

      The setTrackSelectionParameters(TrackSelectionParameters) method must only be + called if this command is available.

      See Also:
      Constant Field Values
      @@ -3217,7 +3384,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • COMMAND_GET_TRACKS

      static final int COMMAND_GET_TRACKS
      -
      Command to get details of the current track selection.
      +
      Command to get details of the current track selection. + +

      The getCurrentTracks() method must only be called if this command is available.

      See Also:
      Constant Field Values
      @@ -3256,7 +3425,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      getApplicationLooper

      Looper getApplicationLooper()
      Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
      + player and on which player events are received. + +

      This method can be called from any thread.

    • @@ -3268,7 +3439,9 @@ static final int COMMAND_SEEK_TO_WINDOW
      void addListener​(Player.Listener listener)
      Registers a listener to receive all events from the player. -

      The listener's methods will be called on the thread associated with getApplicationLooper().

      +

      The listener's methods will be called on the thread associated with getApplicationLooper(). + +

      This method can be called from any thread.

      Parameters:
      listener - The listener to register.
      @@ -3297,11 +3470,13 @@ static final int COMMAND_SEEK_TO_WINDOW
    • setMediaItems

      void setMediaItems​(List<MediaItem> mediaItems)
      -
      Clears the playlist, adds the specified MediaItems and resets the position to - the default position.
      +
      Clears the playlist, adds the specified media items and resets the + position to the default position. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
    • @@ -3313,10 +3488,12 @@ static final int COMMAND_SEEK_TO_WINDOW

      setMediaItems

      void setMediaItems​(List<MediaItem> mediaItems,
                          boolean resetPosition)
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by getCurrentMediaItemIndex() and getCurrentPosition().
      @@ -3332,10 +3509,12 @@ static final int COMMAND_SEEK_TO_WINDOW
      void setMediaItems​(List<MediaItem> mediaItems,
                          int startIndex,
                          long startPositionMs)
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -3355,7 +3534,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      setMediaItem

      void setMediaItem​(MediaItem mediaItem)
      Clears the playlist, adds the specified MediaItem and resets the position to the - default position.
      + default position. + +

      This method must only be called if COMMAND_SET_MEDIA_ITEM is available.

      Parameters:
      mediaItem - The new MediaItem.
      @@ -3370,7 +3551,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      setMediaItem

      void setMediaItem​(MediaItem mediaItem,
                         long startPositionMs)
      -
      Clears the playlist and adds the specified MediaItem.
      +
      Clears the playlist and adds the specified MediaItem. + +

      This method must only be called if COMMAND_SET_MEDIA_ITEM is available.

      Parameters:
      mediaItem - The new MediaItem.
      @@ -3386,7 +3569,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      setMediaItem

      void setMediaItem​(MediaItem mediaItem,
                         boolean resetPosition)
      -
      Clears the playlist and adds the specified MediaItem.
      +
      Clears the playlist and adds the specified MediaItem. + +

      This method must only be called if COMMAND_SET_MEDIA_ITEM is available.

      Parameters:
      mediaItem - The new MediaItem.
      @@ -3403,7 +3588,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • addMediaItem

      void addMediaItem​(MediaItem mediaItem)
      -
      Adds a media item to the end of the playlist.
      +
      Adds a media item to the end of the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      mediaItem - The MediaItem to add.
      @@ -3418,7 +3605,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      addMediaItem

      void addMediaItem​(int index,
                         MediaItem mediaItem)
      -
      Adds a media item at the given index of the playlist.
      +
      Adds a media item at the given index of the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      index - The index at which to add the media item. If the index is larger than the size of @@ -3434,10 +3623,12 @@ static final int COMMAND_SEEK_TO_WINDOW
    • addMediaItems

      void addMediaItems​(List<MediaItem> mediaItems)
      -
      Adds a list of media items to the end of the playlist.
      +
      Adds a list of media items to the end of the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
    • @@ -3449,12 +3640,14 @@ static final int COMMAND_SEEK_TO_WINDOW

      addMediaItems

      void addMediaItems​(int index,
                          List<MediaItem> mediaItems)
      -
      Adds a list of media items at the given index of the playlist.
      +
      Adds a list of media items at the given index of the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
    • @@ -3466,10 +3659,13 @@ static final int COMMAND_SEEK_TO_WINDOW

      moveMediaItem

      void moveMediaItem​(int currentIndex,
                          int newIndex)
      -
      Moves the media item at the current index to the new index.
      +
      Moves the media item at the current index to the new index. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      currentIndex - The current index of the media item to move.
      +
      currentIndex - The current index of the media item to move. If the index is larger than + the size of the playlist, the request is ignored.
      newIndex - The new index of the media item. If the new index is larger than the size of the playlist the item is moved to the end of the playlist.
      @@ -3484,11 +3680,15 @@ static final int COMMAND_SEEK_TO_WINDOW
      void moveMediaItems​(int fromIndex,
                           int toIndex,
                           int newIndex)
      -
      Moves the media item range to the new index.
      +
      Moves the media item range to the new index. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -3502,10 +3702,13 @@ static final int COMMAND_SEEK_TO_WINDOW
    • removeMediaItem

      void removeMediaItem​(int index)
      -
      Removes the media item at the given index of the playlist.
      +
      Removes the media item at the given index of the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      index - The index at which to remove the media item.
      +
      index - The index at which to remove the media item. If the index is larger than the size + of the playlist, the request is ignored.
    • @@ -3517,12 +3720,15 @@ static final int COMMAND_SEEK_TO_WINDOW

      removeMediaItems

      void removeMediaItems​(int fromIndex,
                             int toIndex)
      -
      Removes a range of media items from the playlist.
      +
      Removes a range of media items from the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -3533,7 +3739,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • clearMediaItems

      void clearMediaItems()
      -
      Clears the playlist.
      +
      Clears the playlist. + +

      This method must only be called if COMMAND_CHANGE_MEDIA_ITEMS is available.

    • @@ -3546,13 +3754,7 @@ static final int COMMAND_SEEK_TO_WINDOW @com.google.android.exoplayer2.Player.Command int command)
      Returns whether the provided Player.Command is available. -

      This method does not execute the command. - -

      Executing a command that is not available (for example, calling seekToNextMediaItem() if COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a getPlayerError() player error}. - -

      COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem.

      +

      This method does not execute the command.

      Parameters:
      command - A Player.Command.
      @@ -3583,13 +3785,7 @@ static final int COMMAND_SEEK_TO_WINDOW
      Returns the player's currently available Player.Commands.

      The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands - change. - -

      Executing a command that is not available (for example, calling seekToNextMediaItem() if COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a getPlayerError() player error}. - -

      COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem.

      + change.
      Returns:
      The currently available Player.Commands.
      @@ -3607,6 +3803,8 @@ static final int COMMAND_SEEK_TO_WINDOW
      void prepare()
      Prepares the player. +

      This method must only be called if COMMAND_PREPARE is available. +

      This will move the player out of idle state and the player will start loading media and acquire resources needed for playback.

      @@ -3619,10 +3817,10 @@ static final int COMMAND_SEEK_TO_WINDOW

      getPlaybackState

      @State
       @com.google.android.exoplayer2.Player.State int getPlaybackState()
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -3640,7 +3838,7 @@ static final int COMMAND_SEEK_TO_WINDOW true, or PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -3700,7 +3898,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      play

      void play()
      Resumes playback as soon as getPlaybackState() == STATE_READY. Equivalent to - setPlayWhenReady(true).
      + setPlayWhenReady(true). + +

      This method must only be called if COMMAND_PLAY_PAUSE is available. @@ -3710,7 +3910,9 @@ static final int COMMAND_SEEK_TO_WINDOW

    • pause

      void pause()
      -
      Pauses playback. Equivalent to setPlayWhenReady(false).
      +
    • @@ -3722,7 +3924,9 @@ static final int COMMAND_SEEK_TO_WINDOW
      void setPlayWhenReady​(boolean playWhenReady)
      Sets whether playback should proceed when getPlaybackState() == STATE_READY. -

      If the player is already in the ready state then this method pauses and resumes playback.

      +

      If the player is already in the ready state then this method pauses and resumes playback. + +

      This method must only be called if COMMAND_PLAY_PAUSE is available.

      Parameters:
      playWhenReady - Whether playback should proceed when ready.
      @@ -3753,7 +3957,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      setRepeatMode

      void setRepeatMode​(@RepeatMode
                          @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      -
      Sets the Player.RepeatMode to be used for playback.
      +
      Sets the Player.RepeatMode to be used for playback. + +

      This method must only be called if COMMAND_SET_REPEAT_MODE is available.

      Parameters:
      repeatMode - The repeat mode.
      @@ -3784,7 +3990,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • setShuffleModeEnabled

      void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      -
      Sets whether shuffling of media items is enabled.
      +
      Sets whether shuffling of media items is enabled. + +

      This method must only be called if COMMAND_SET_SHUFFLE_MODE is available.

      Parameters:
      shuffleModeEnabled - Whether shuffling is enabled.
      @@ -3830,7 +4038,9 @@ static final int COMMAND_SEEK_TO_WINDOW
      void seekToDefaultPosition()
      Seeks to the default position associated with the current MediaItem. The position can depend on the type of media being played. For live streams it will typically be the live edge. - For other streams it will typically be the start.
      + For other streams it will typically be the start. + +

      This method must only be called if COMMAND_SEEK_TO_DEFAULT_POSITION is available.

    • @@ -3842,14 +4052,14 @@ static final int COMMAND_SEEK_TO_WINDOW
      void seekToDefaultPosition​(int mediaItemIndex)
      Seeks to the default position associated with the specified MediaItem. The position can depend on the type of media being played. For live streams it will typically be the live edge. - For other streams it will typically be the start.
      + For other streams it will typically be the start. + +

      This method must only be called if COMMAND_SEEK_TO_MEDIA_ITEM is available.

      Parameters:
      mediaItemIndex - The index of the MediaItem whose associated default position - should be seeked to.
      -
      Throws:
      -
      IllegalSeekPositionException - If the player has a non-empty timeline and the provided - mediaItemIndex is not within the bounds of the current timeline.
      + should be seeked to. If the index is larger than the size of the playlist, the request is + ignored.
      @@ -3860,7 +4070,10 @@ static final int COMMAND_SEEK_TO_WINDOW
    • seekTo

      void seekTo​(long positionMs)
      -
      Seeks to a position specified in milliseconds in the current MediaItem.
      +
      Seeks to a position specified in milliseconds in the current MediaItem. + +

      This method must only be called if COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM is + available.

      Parameters:
      positionMs - The seek position in the current MediaItem, or C.TIME_UNSET @@ -3876,15 +4089,15 @@ static final int COMMAND_SEEK_TO_WINDOW

      seekTo

      void seekTo​(int mediaItemIndex,
                   long positionMs)
      -
      Seeks to a position specified in milliseconds in the specified MediaItem.
      +
      Seeks to a position specified in milliseconds in the specified MediaItem. + +

      This method must only be called if COMMAND_SEEK_TO_MEDIA_ITEM is available.

      Parameters:
      -
      mediaItemIndex - The index of the MediaItem.
      +
      mediaItemIndex - The index of the MediaItem. If the index is larger than the size + of the playlist, the request is ignored.
      positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET to seek to the media item's default position.
      -
      Throws:
      -
      IllegalSeekPositionException - If the player has a non-empty timeline and the provided - mediaItemIndex is not within the bounds of the current timeline.
    • @@ -3911,7 +4124,9 @@ static final int COMMAND_SEEK_TO_WINDOW
    • seekBack

      void seekBack()
      -
      Seeks back in the current MediaItem by getSeekBackIncrement() milliseconds.
      +
      Seeks back in the current MediaItem by getSeekBackIncrement() milliseconds. + +

      This method must only be called if COMMAND_SEEK_BACK is available.

    • @@ -3938,7 +4153,9 @@ static final int COMMAND_SEEK_TO_WINDOW

      seekForward

      void seekForward()
      + milliseconds. + +

      This method must only be called if COMMAND_SEEK_FORWARD is available. @@ -3979,7 +4196,9 @@ boolean hasPreviousWindow()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_GET_TIMELINE is available. @@ -4020,7 +4239,10 @@ void seekToPreviousWindow()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM is + available. @@ -4060,7 +4282,9 @@ void seekToPreviousWindow()

    • Otherwise, if a previous media item exists and the current position is less than getMaxSeekToPreviousPosition(), seeks to the default position of the previous MediaItem.
    • Otherwise, seeks to 0 in the current MediaItem. - + + +

      This method must only be called if COMMAND_SEEK_TO_PREVIOUS is available.

    • @@ -4101,7 +4325,9 @@ boolean hasNextWindow()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_GET_TIMELINE is available. @@ -4143,7 +4369,9 @@ void seekToNextWindow()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is available. @@ -4163,7 +4391,9 @@ void seekToNextWindow()

    • Otherwise, if the current MediaItem is live and has not ended, seeks to the live edge of the current MediaItem.
    • Otherwise, does nothing. - + + +

      This method must only be called if COMMAND_SEEK_TO_NEXT is available.

    • @@ -4177,7 +4407,9 @@ void seekToNextWindow() player to the default, which means there is no speed or pitch adjustment.

      Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently - active playback parameters change. + active playback parameters change. + +

      This method must only be called if COMMAND_SET_SPEED_AND_PITCH is available.

      Parameters:
      playbackParameters - The playback parameters.
      @@ -4195,11 +4427,13 @@ void seekToNextWindow()
      Changes the rate at which playback occurs. The pitch is not changed.

      This is equivalent to - setPlaybackParameters(getPlaybackParameters().withSpeed(speed)).

      + setPlaybackParameters(getPlaybackParameters().withSpeed(speed)). + +

      This method must only be called if COMMAND_SET_SPEED_AND_PITCH is available.

      Parameters:
      speed - The linear factor by which playback will be sped up. Must be higher than 0. 1 is - normal speed, 2 is twice as fast, 0.5 is half normal speed...
      + normal speed, 2 is twice as fast, 0.5 is half normal speed.
      @@ -4233,7 +4467,9 @@ void seekToNextWindow() still be called on the player if it's no longer required.

      Calling this method does not clear the playlist, reset the playback position or the playback - error. + error. + +

      This method must only be called if COMMAND_STOP is available. @@ -4269,7 +4505,9 @@ void stop​(boolean reset)

    • getCurrentTracks

      Tracks getCurrentTracks()
      -
      Returns the current tracks.
      +
      Returns the current tracks. + +

      This method must only be called if COMMAND_GET_TRACKS is available.

      See Also:
      Player.Listener.onTracksChanged(Tracks)
      @@ -4311,7 +4549,10 @@ void stop​(boolean reset) .buildUpon() .setMaxVideoSizeSd() .build()) - + + +

      This method must only be called if COMMAND_SET_TRACK_SELECTION_PARAMETERS is + available.

    • @@ -4327,7 +4568,9 @@ void stop​(boolean reset)

      This MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, - it will be prioritised above the same field coming from static or timed metadata. + it will be prioritised above the same field coming from static or timed metadata. + +

      This method must only be called if COMMAND_GET_MEDIA_ITEMS_METADATA is available. @@ -4337,7 +4580,9 @@ void stop​(boolean reset)

    • getPlaylistMetadata

      MediaMetadata getPlaylistMetadata()
      -
      Returns the playlist MediaMetadata, as set by setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      +
      Returns the playlist MediaMetadata, as set by setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported. + +

      This method must only be called if COMMAND_GET_MEDIA_ITEMS_METADATA is available.

    • @@ -4347,7 +4592,9 @@ void stop​(boolean reset)
    • setPlaylistMetadata

      void setPlaylistMetadata​(MediaMetadata mediaMetadata)
      -
      Sets the playlist MediaMetadata.
      +
      Sets the playlist MediaMetadata. + +

      This method must only be called if COMMAND_SET_MEDIA_ITEMS_METADATA is available.

    • @@ -4368,7 +4615,9 @@ void stop​(boolean reset)
    • getCurrentTimeline

      Timeline getCurrentTimeline()
      -
      Returns the current Timeline. Never null, but may be empty.
      +
      Returns the current Timeline. Never null, but may be empty. + +

      This method must only be called if COMMAND_GET_TIMELINE is available.

      See Also:
      Player.Listener.onTimelineChanged(Timeline, int)
      @@ -4382,7 +4631,9 @@ void stop​(boolean reset)
    • getCurrentPeriodIndex

      int getCurrentPeriodIndex()
      -
      Returns the index of the period currently being played.
      +
      Returns the index of the period currently being played. + +

      This method must only be called if COMMAND_GET_TIMELINE is available.

    • @@ -4406,7 +4657,9 @@ int getCurrentWindowIndex()

      getCurrentMediaItemIndex

      int getCurrentMediaItemIndex()
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is - empty.
      + empty. + +

      This method must only be called if COMMAND_GET_TIMELINE is available.

    • @@ -4435,7 +4688,9 @@ int getNextWindowIndex()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_GET_TIMELINE is available. @@ -4464,7 +4719,9 @@ int getPreviousWindowIndex()

      Note: When the repeat mode is REPEAT_MODE_ONE, this method behaves the same as when the current repeat mode is REPEAT_MODE_OFF. See REPEAT_MODE_ONE for more - details. + details. + +

      This method must only be called if COMMAND_GET_TIMELINE is available. @@ -4475,7 +4732,9 @@ int getPreviousWindowIndex()

      getCurrentMediaItem

      @Nullable
       MediaItem getCurrentMediaItem()
      -
      Returns the currently playing MediaItem. May be null if the timeline is empty.
      +
      Returns the currently playing MediaItem. May be null if the timeline is empty. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      See Also:
      Player.Listener.onMediaItemTransition(MediaItem, int)
      @@ -4489,7 +4748,9 @@ int getPreviousWindowIndex()
    • getMediaItemCount

      int getMediaItemCount()
      -
      Returns the number of media items in the playlist.
      +
      Returns the number of media items in the playlist. + +

      This method must only be called if COMMAND_GET_TIMELINE is available.

    • @@ -4499,7 +4760,9 @@ int getPreviousWindowIndex()
    • getMediaItemAt

      MediaItem getMediaItemAt​(int index)
      -
      Returns the MediaItem at the given index.
      +
      Returns the MediaItem at the given index. + +

      This method must only be called if COMMAND_GET_TIMELINE is available.

    • @@ -4510,7 +4773,9 @@ int getPreviousWindowIndex()

      getDuration

      long getDuration()
      + the duration is not known. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4521,7 +4786,9 @@ int getPreviousWindowIndex()

      getCurrentPosition

      long getCurrentPosition()
      + position in milliseconds if the current timeline is empty. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4532,7 +4799,9 @@ int getPreviousWindowIndex()

      getBufferedPosition

      long getBufferedPosition()
      Returns an estimate of the position in the current content or ad up to which data is buffered, - in milliseconds.
      + in milliseconds. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4556,7 +4825,9 @@ int getBufferedPercentage()

      getTotalBufferedDuration

      long getTotalBufferedDuration()
      + This includes pre-buffered data for subsequent ads and media items. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4580,7 +4851,9 @@ boolean isCurrentWindowDynamic()

      isCurrentMediaItemDynamic

      boolean isCurrentMediaItemDynamic()
      Returns whether the current MediaItem is dynamic (may change when the Timeline - is updated), or false if the Timeline is empty.
      + is updated), or false if the Timeline is empty. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      See Also:
      Timeline.Window.isDynamic
      @@ -4608,7 +4881,9 @@ boolean isCurrentWindowLive()

      isCurrentMediaItemLive

      boolean isCurrentMediaItemLive()
      Returns whether the current MediaItem is live, or false if the Timeline - is empty.
      + is empty. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      See Also:
      Timeline.Window.isLive()
      @@ -4630,7 +4905,9 @@ boolean isCurrentWindowLive() positive.

      Note that this offset may rely on an accurate local time, so this method may return an - incorrect value if the difference between system clock and server clock is unknown. + incorrect value if the difference between system clock and server clock is unknown. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4653,7 +4930,9 @@ boolean isCurrentWindowSeekable()

    • isCurrentMediaItemSeekable

      boolean isCurrentMediaItemSeekable()
      -
      Returns whether the current MediaItem is seekable, or false if the Timeline is empty.
      +
      Returns whether the current MediaItem is seekable, or false if the Timeline is empty. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      See Also:
      Timeline.Window.isSeekable
      @@ -4667,7 +4946,9 @@ boolean isCurrentWindowSeekable()
    • isPlayingAd

      boolean isPlayingAd()
      -
      Returns whether the player is currently playing an ad.
      +
      Returns whether the player is currently playing an ad. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

    • @@ -4678,7 +4959,9 @@ boolean isCurrentWindowSeekable()

      getCurrentAdGroupIndex

      int getCurrentAdGroupIndex()
      If isPlayingAd() returns true, returns the index of the ad group in the period - currently being played. Returns C.INDEX_UNSET otherwise.
      + currently being played. Returns C.INDEX_UNSET otherwise. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available.

    • @@ -4689,7 +4972,9 @@ boolean isCurrentWindowSeekable()

      getCurrentAdIndexInAdGroup

      int getCurrentAdIndexInAdGroup()
      If isPlayingAd() returns true, returns the index of the ad in its ad group. Returns - C.INDEX_UNSET otherwise.
      + C.INDEX_UNSET otherwise. + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4701,7 +4986,9 @@ boolean isCurrentWindowSeekable()

      long getContentDuration()
      If isPlayingAd() returns true, returns the duration of the current content in milliseconds, or C.TIME_UNSET if the duration is not known. If there is no ad playing, - the returned duration is the same as that returned by getDuration().
      + the returned duration is the same as that returned by getDuration(). + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4713,7 +5000,9 @@ boolean isCurrentWindowSeekable()

      long getContentPosition()
      If isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by getCurrentPosition().
      + playing, the returned position is the same as that returned by getCurrentPosition(). + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4725,7 +5014,9 @@ boolean isCurrentWindowSeekable()

      long getContentBufferedPosition()
      If isPlayingAd() returns true, returns an estimate of the content position in the current content up to which data is buffered, in milliseconds. If there is no ad playing, - the returned position is the same as that returned by getBufferedPosition().
      + the returned position is the same as that returned by getBufferedPosition(). + +

      This method must only be called if COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -4735,7 +5026,9 @@ boolean isCurrentWindowSeekable()

    • getAudioAttributes

      AudioAttributes getAudioAttributes()
      -
      Returns the attributes for audio playback.
      +
      Returns the attributes for audio playback. + +

      This method must only be called if COMMAND_GET_AUDIO_ATTRIBUTES is available.

    • @@ -4747,7 +5040,9 @@ boolean isCurrentWindowSeekable()
      void setVolume​(@FloatRange(from=0.0,to=1.0)
                      float volume)
      Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal - unchanged), inclusive.
      + unchanged), inclusive. + +

      This method must only be called if COMMAND_SET_VOLUME is available.

      Parameters:
      volume - Linear output gain to apply to all audio channels.
      @@ -4763,7 +5058,9 @@ boolean isCurrentWindowSeekable()
      @FloatRange(from=0.0,
                   to=1.0)
       float getVolume()
      -
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      +
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + +

      This method must only be called if COMMAND_GET_VOLUME is available.

      Returns:
      The linear gain applied to all audio channels.
      @@ -4778,7 +5075,9 @@ float getVolume()

      clearVideoSurface

      void clearVideoSurface()
      Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
      + currently set on the player. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available. @@ -4790,7 +5089,9 @@ float getVolume()

      void clearVideoSurface​(@Nullable
                              Surface surface)
      Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surface - The surface to clear.
      @@ -4811,7 +5112,9 @@ float getVolume()

      If the surface is held by a SurfaceView, TextureView or SurfaceHolder then it's recommended to use setVideoSurfaceView(SurfaceView), setVideoTextureView(TextureView) or setVideoSurfaceHolder(SurfaceHolder) rather than this method, since passing the holder allows the player to track the lifecycle of the surface - automatically. + automatically. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surface - The Surface.
      @@ -4830,7 +5133,9 @@ float getVolume() rendered. The player will track the lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with getApplicationLooper(). + associated with getApplicationLooper(). + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceHolder - The surface holder.
      @@ -4846,7 +5151,9 @@ float getVolume()
      void clearVideoSurfaceHolder​(@Nullable
                                    SurfaceHolder surfaceHolder)
      Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
      + rendered if it matches the one passed. Else does nothing. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceHolder - The surface holder to clear.
      @@ -4865,7 +5172,9 @@ float getVolume() lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with getApplicationLooper(). + associated with getApplicationLooper(). + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceView - The surface view.
      @@ -4881,7 +5190,9 @@ float getVolume()
      void clearVideoSurfaceView​(@Nullable
                                  SurfaceView surfaceView)
      Clears the SurfaceView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceView - The texture view to clear.
      @@ -4900,7 +5211,9 @@ float getVolume() lifecycle of the surface automatically.

      The thread that calls the TextureView.SurfaceTextureListener methods must be the - thread associated with getApplicationLooper(). + thread associated with getApplicationLooper(). + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view.
      @@ -4916,7 +5229,9 @@ float getVolume()
      void clearVideoTextureView​(@Nullable
                                  TextureView textureView)
      Clears the TextureView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view to clear.
      @@ -4961,7 +5276,9 @@ float getVolume()
    • getCurrentCues

      CueGroup getCurrentCues()
      -
      Returns the current CueGroup.
      +
      Returns the current CueGroup. + +

      This method must only be called if COMMAND_GET_TEXT is available.

    • @@ -4990,7 +5307,9 @@ int getDeviceVolume() Util.getStreamTypeForAudioUsage(int).

      For devices with remote playback, the volume of the - remote device is returned. + remote device is returned. + +

      This method must only be called if COMMAND_GET_DEVICE_VOLUME is available. @@ -5000,7 +5319,9 @@ int getDeviceVolume()

    • isDeviceMuted

      boolean isDeviceMuted()
      -
      Gets whether the device is muted or not.
      +
    • @@ -5011,7 +5332,9 @@ int getDeviceVolume()

      setDeviceVolume

      void setDeviceVolume​(@IntRange(from=0L)
                            int volume)
      -
      Sets the volume of the device.
      +
      Parameters:
      volume - The volume to set.
      @@ -5025,7 +5348,9 @@ int getDeviceVolume()
    • increaseDeviceVolume

      void increaseDeviceVolume()
      -
      Increases the volume of the device.
      +
      Increases the volume of the device. + +

      This method must only be called if COMMAND_ADJUST_DEVICE_VOLUME is available.

    • @@ -5035,7 +5360,9 @@ int getDeviceVolume()
    • decreaseDeviceVolume

      void decreaseDeviceVolume()
      -
      Decreases the volume of the device.
      +
    • @@ -5045,7 +5372,9 @@ int getDeviceVolume()
    • setDeviceMuted

      void setDeviceMuted​(boolean muted)
      -
      Sets the mute state of the device.
      +
    • diff --git a/docs/doc/reference/com/google/android/exoplayer2/RendererCapabilities.html b/docs/doc/reference/com/google/android/exoplayer2/RendererCapabilities.html index 6169dca9ca..9fa2e0be7c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/RendererCapabilities.html +++ b/docs/doc/reference/com/google/android/exoplayer2/RendererCapabilities.html @@ -242,15 +242,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); static int DECODER_SUPPORT_FALLBACK -
      The renderer will use a fallback decoder.
      +
      The format exceeds the primary decoder's capabilities but is supported by fallback decoder
      static int DECODER_SUPPORT_FALLBACK_MIMETYPE -
      The renderer will use a decoder for fallback mimetype if possible as format's MIME type is - unsupported
      +
      The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME + type.
      @@ -748,8 +748,8 @@ static final int FORMAT_UNSUPPORTED_TYPE
    • DECODER_SUPPORT_FALLBACK_MIMETYPE

      static final int DECODER_SUPPORT_FALLBACK_MIMETYPE
      -
      The renderer will use a decoder for fallback mimetype if possible as format's MIME type is - unsupported
      +
      The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME + type.
      See Also:
      Constant Field Values
      @@ -777,7 +777,7 @@ static final int FORMAT_UNSUPPORTED_TYPE
    • DECODER_SUPPORT_FALLBACK

      static final int DECODER_SUPPORT_FALLBACK
      -
      The renderer will use a fallback decoder.
      +
      The format exceeds the primary decoder's capabilities but is supported by fallback decoder
      See Also:
      Constant Field Values
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.State.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.State.Builder.html index 3b802f0a26..4ce001a524 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.State.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.State.Builder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -188,12 +188,179 @@ extends SimpleBasePlayer.State.Builder +clearPositionDiscontinuity() + +
      Clears a previously set position discontinuity signal.
      + + + +SimpleBasePlayer.State.Builder +setAdBufferedPositionMs​(SimpleBasePlayer.PositionSupplier adBufferedPositionMsSupplier) + +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently + playing ad is buffered, in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setAdPositionMs​(long positionMs) + +
      Sets the current ad playback position in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setAdPositionMs​(SimpleBasePlayer.PositionSupplier adPositionMsSupplier) + +
      Sets the SimpleBasePlayer.PositionSupplier for the current ad playback position in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setAudioAttributes​(AudioAttributes audioAttributes) + +
      Sets the current AudioAttributes.
      + + + +SimpleBasePlayer.State.Builder setAvailableCommands​(Player.Commands availableCommands)
      Sets the available Player.Commands.
      - + +SimpleBasePlayer.State.Builder +setContentBufferedPositionMs​(SimpleBasePlayer.PositionSupplier contentBufferedPositionMsSupplier) + +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently + playing content is buffered, in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setContentPositionMs​(long positionMs) + +
      Sets the current content playback position in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setContentPositionMs​(SimpleBasePlayer.PositionSupplier contentPositionMsSupplier) + +
      Sets the SimpleBasePlayer.PositionSupplier for the current content playback position in + milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setCurrentAd​(int adGroupIndex, + int adIndexInAdGroup) + +
      Sets the current ad indices, or C.INDEX_UNSET if no ad is playing.
      + + + +SimpleBasePlayer.State.Builder +setCurrentCues​(CueGroup currentCues) + +
      Sets the current cues.
      + + + +SimpleBasePlayer.State.Builder +setCurrentMediaItemIndex​(int currentMediaItemIndex) + +
      Sets the current media item index.
      + + + +SimpleBasePlayer.State.Builder +setDeviceInfo​(DeviceInfo deviceInfo) + +
      Sets the DeviceInfo.
      + + + +SimpleBasePlayer.State.Builder +setDeviceVolume​(int deviceVolume) + +
      Sets the current device volume.
      + + + +SimpleBasePlayer.State.Builder +setIsDeviceMuted​(boolean isDeviceMuted) + +
      Sets whether the device is muted.
      + + + +SimpleBasePlayer.State.Builder +setIsLoading​(boolean isLoading) + +
      Sets whether the player is currently loading its source.
      + + + +SimpleBasePlayer.State.Builder +setMaxSeekToPreviousPositionMs​(long maxSeekToPreviousPositionMs) + +
      Sets the maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, + in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setNewlyRenderedFirstFrame​(boolean newlyRenderedFirstFrame) + +
      Sets whether a frame has been rendered for the first time since setting the surface, a + rendering reset, or since the stream being rendered was changed.
      + + + +SimpleBasePlayer.State.Builder +setPlaybackParameters​(PlaybackParameters playbackParameters) + +
      Sets the currently active PlaybackParameters.
      + + + +SimpleBasePlayer.State.Builder +setPlaybackState​(@com.google.android.exoplayer2.Player.State int playbackState) + +
      Sets the state of the player.
      + + + +SimpleBasePlayer.State.Builder +setPlaybackSuppressionReason​(@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int playbackSuppressionReason) + +
      Sets the reason why playback is suppressed even if SimpleBasePlayer.getPlayWhenReady() is true.
      + + + +SimpleBasePlayer.State.Builder +setPlayerError​(PlaybackException playerError) + +
      Sets last error that caused playback to fail, or null if there was no error.
      + + + +SimpleBasePlayer.State.Builder +setPlaylist​(List<SimpleBasePlayer.MediaItemData> playlist) + +
      Sets the list of media items in the playlist.
      + + + +SimpleBasePlayer.State.Builder +setPlaylistMetadata​(MediaMetadata playlistMetadata) + +
      Sets the playlist MediaMetadata.
      + + + SimpleBasePlayer.State.Builder setPlayWhenReady​(boolean playWhenReady, @com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) @@ -201,6 +368,87 @@ extends Sets whether playback should proceed when ready and not suppressed. + +SimpleBasePlayer.State.Builder +setPositionDiscontinuity​(@com.google.android.exoplayer2.Player.DiscontinuityReason int positionDiscontinuityReason, + long discontinuityPositionMs) + +
      Signals that a position discontinuity happened since the last player update and sets the + reason for it.
      + + + +SimpleBasePlayer.State.Builder +setRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode) + +
      Sets the Player.RepeatMode used for playback.
      + + + +SimpleBasePlayer.State.Builder +setSeekBackIncrementMs​(long seekBackIncrementMs) + +
      Sets the Player.seekBack() increment in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setSeekForwardIncrementMs​(long seekForwardIncrementMs) + +
      Sets the Player.seekForward() increment in milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setShuffleModeEnabled​(boolean shuffleModeEnabled) + +
      Sets whether shuffling of media items is enabled.
      + + + +SimpleBasePlayer.State.Builder +setSurfaceSize​(Size surfaceSize) + +
      Sets the size of the surface onto which the video is being rendered.
      + + + +SimpleBasePlayer.State.Builder +setTimedMetadata​(Metadata timedMetadata) + +
      Sets the most recent timed Metadata.
      + + + +SimpleBasePlayer.State.Builder +setTotalBufferedDurationMs​(SimpleBasePlayer.PositionSupplier totalBufferedDurationMsSupplier) + +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated total buffered duration in + milliseconds.
      + + + +SimpleBasePlayer.State.Builder +setTrackSelectionParameters​(TrackSelectionParameters trackSelectionParameters) + +
      Sets the currently active TrackSelectionParameters.
      + + + +SimpleBasePlayer.State.Builder +setVideoSize​(VideoSize videoSize) + +
      Sets the current video size.
      + + + +SimpleBasePlayer.State.Builder +setVolume​(float volume) + +
      Sets the current audio volume, with 0 being silence and 1 being unity gain (signal + unchanged).
      + + @@ -284,7 +537,7 @@ extends -
        +
        • playWhenReadyChangeReason

          @PlayWhenReadyChangeReason
          @@ -292,6 +545,375 @@ public final @com.google.android.exoplayer2.Player.PlayWhenReadyChangeReaso
           
          The last reason for changing playWhenReady.
        + + + +
          +
        • +

          playbackState

          +
          @State
          +public final @com.google.android.exoplayer2.Player.State int playbackState
          +
          The state of the player.
          +
        • +
        + + + + + + + +
          +
        • +

          playerError

          +
          @Nullable
          +public final PlaybackException playerError
          +
          The last error that caused playback to fail, or null if there was no error.
          +
        • +
        + + + +
          +
        • +

          repeatMode

          +
          @RepeatMode
          +public final @com.google.android.exoplayer2.Player.RepeatMode int repeatMode
          +
          The Player.RepeatMode used for playback.
          +
        • +
        + + + +
          +
        • +

          shuffleModeEnabled

          +
          public final boolean shuffleModeEnabled
          +
          Whether shuffling of media items is enabled.
          +
        • +
        + + + +
          +
        • +

          isLoading

          +
          public final boolean isLoading
          +
          Whether the player is currently loading its source.
          +
        • +
        + + + +
          +
        • +

          seekBackIncrementMs

          +
          public final long seekBackIncrementMs
          +
          The Player.seekBack() increment in milliseconds.
          +
        • +
        + + + +
          +
        • +

          seekForwardIncrementMs

          +
          public final long seekForwardIncrementMs
          +
          The Player.seekForward() increment in milliseconds.
          +
        • +
        + + + +
          +
        • +

          maxSeekToPreviousPositionMs

          +
          public final long maxSeekToPreviousPositionMs
          +
          The maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, in + milliseconds.
          +
        • +
        + + + + + + + + + + + + + + + +
          +
        • +

          volume

          +
          @FloatRange(from=0.0,
          +            to=1.0)
          +public final float volume
          +
          The current audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
          +
        • +
        + + + +
          +
        • +

          videoSize

          +
          public final VideoSize videoSize
          +
          The current video size.
          +
        • +
        + + + +
          +
        • +

          currentCues

          +
          public final CueGroup currentCues
          +
          The current cues.
          +
        • +
        + + + + + + + +
          +
        • +

          deviceVolume

          +
          @IntRange(from=0L)
          +public final int deviceVolume
          +
          The current device volume.
          +
        • +
        + + + +
          +
        • +

          isDeviceMuted

          +
          public final boolean isDeviceMuted
          +
          Whether the device is muted.
          +
        • +
        + + + +
          +
        • +

          surfaceSize

          +
          public final Size surfaceSize
          +
          The size of the surface onto which the video is being rendered.
          +
        • +
        + + + +
          +
        • +

          newlyRenderedFirstFrame

          +
          public final boolean newlyRenderedFirstFrame
          +
          Whether a frame has been rendered for the first time since setting the surface, a rendering + reset, or since the stream being rendered was changed.
          +
        • +
        + + + +
          +
        • +

          timedMetadata

          +
          public final Metadata timedMetadata
          +
          The most recent timed metadata.
          +
        • +
        + + + + + + + + + + + + + + + +
          +
        • +

          currentMediaItemIndex

          +
          public final int currentMediaItemIndex
          +
          The current media item index, or C.INDEX_UNSET to assume the default first item of + the playlist is played.
          +
        • +
        + + + +
          +
        • +

          currentAdGroupIndex

          +
          public final int currentAdGroupIndex
          +
          The current ad group index, or C.INDEX_UNSET if no ad is playing.
          +
        • +
        + + + +
          +
        • +

          currentAdIndexInAdGroup

          +
          public final int currentAdIndexInAdGroup
          +
          The current ad index in the ad group, or C.INDEX_UNSET if no ad is playing.
          +
        • +
        + + + + + + + + + + + + + + + + + + + + + + + +
          +
        • +

          hasPositionDiscontinuity

          +
          public final boolean hasPositionDiscontinuity
          +
          Signals that a position discontinuity happened since the last update to the player.
          +
        • +
        + + + +
          +
        • +

          positionDiscontinuityReason

          +
          @DiscontinuityReason
          +public final @com.google.android.exoplayer2.Player.DiscontinuityReason int positionDiscontinuityReason
          +
          The reason for the last position discontinuity. The + value is unused if hasPositionDiscontinuity is false.
          +
        • +
        + + + +
          +
        • +

          discontinuityPositionMs

          +
          public final long discontinuityPositionMs
          +
          The position, in milliseconds, in the current content or ad from which playback continued + after the discontinuity. The value is unused if hasPositionDiscontinuity is + false.
          +
        • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.html index f3c093bf29..f4f42c3f7c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleBasePlayer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":6,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":10,"i72":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":6,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":10,"i72":10,"i73":10,"i74":10,"i75":10,"i76":10,"i77":10,"i78":10,"i79":10,"i80":10,"i81":10,"i82":10,"i83":10,"i84":10,"i85":10,"i86":10,"i87":10,"i88":10,"i89":10,"i90":10,"i91":10,"i92":10,"i93":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -198,6 +198,29 @@ extends protected static class  +SimpleBasePlayer.MediaItemData + +
      An immutable description of an item in the playlist, containing both static setup information + like MediaItem and dynamic data that is generally read from the media like the + duration.
      + + + +protected static class  +SimpleBasePlayer.PeriodData + +
      Data describing the properties of a period inside a SimpleBasePlayer.MediaItemData.
      + + + +protected static interface  +SimpleBasePlayer.PositionSupplier + +
      A supplier for a position.
      + + + +protected static class  SimpleBasePlayer.State
      An immutable state description of the player.
      @@ -487,27 +510,35 @@ extends +protected SimpleBasePlayer.MediaItemData +getPlaceholderMediaItemData​(MediaItem mediaItem) + +
      Returns the placeholder SimpleBasePlayer.MediaItemData used for a new MediaItem added to the + playlist.
      + + + protected SimpleBasePlayer.State getPlaceholderState​(SimpleBasePlayer.State suggestedPlaceholderState)
      Returns the placeholder state used while a player method is handled asynchronously.
      - + PlaybackParameters getPlaybackParameters()
      Returns the currently active playback parameters.
      - -int + +@com.google.android.exoplayer2.Player.State int getPlaybackState() -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      - + int getPlaybackSuppressionReason() @@ -515,140 +546,288 @@ extends , or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed. - + PlaybackException getPlayerError()
      Returns the error that caused playback to fail.
      - + MediaMetadata getPlaylistMetadata()
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      - + boolean getPlayWhenReady()
      Whether playback will proceed when Player.getPlaybackState() == Player.STATE_READY.
      - -int + +@com.google.android.exoplayer2.Player.RepeatMode int getRepeatMode()
      Returns the current Player.RepeatMode used for playback.
      - + long getSeekBackIncrement()
      Returns the Player.seekBack() increment.
      - + long getSeekForwardIncrement()
      Returns the Player.seekForward() increment.
      - + boolean getShuffleModeEnabled()
      Returns whether shuffling of media items is enabled.
      - + protected abstract SimpleBasePlayer.State getState()
      Returns the current SimpleBasePlayer.State of the player.
      - + Size getSurfaceSize()
      Gets the size of the surface on which the video is rendered.
      - + long getTotalBufferedDuration()
      Returns an estimate of the total buffered duration from the current position, in milliseconds.
      - + TrackSelectionParameters getTrackSelectionParameters()
      Returns the parameters constraining the track selection.
      - + VideoSize getVideoSize()
      Gets the size of the video.
      - + float getVolume()
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      - + +protected ListenableFuture<?> +handleAddMediaItems​(int index, + List<MediaItem> mediaItems) + + + + + +protected ListenableFuture<?> +handleClearVideoOutput​(Object videoOutput) + +
      Handles calls to clear the video output.
      + + + +protected ListenableFuture<?> +handleDecreaseDeviceVolume() + + + + + +protected ListenableFuture<?> +handleIncreaseDeviceVolume() + + + + + +protected ListenableFuture<?> +handleMoveMediaItems​(int fromIndex, + int toIndex, + int newIndex) + + + + + +protected ListenableFuture<?> +handlePrepare() + +
      Handles calls to Player.prepare().
      + + + +protected ListenableFuture<?> +handleRelease() + +
      Handles calls to Player.release().
      + + + +protected ListenableFuture<?> +handleRemoveMediaItems​(int fromIndex, + int toIndex) + + + + + +protected ListenableFuture<?> +handleSeek​(int mediaItemIndex, + long positionMs, + @com.google.android.exoplayer2.Player.Command int seekCommand) + +
      Handles calls to Player.seekTo(long) and other seek operations (for example, Player.seekToNext()).
      + + + +protected ListenableFuture<?> +handleSetDeviceMuted​(boolean muted) + + + + + +protected ListenableFuture<?> +handleSetDeviceVolume​(int deviceVolume) + +
      Handles calls to Player.setDeviceVolume(int).
      + + + +protected ListenableFuture<?> +handleSetMediaItems​(List<MediaItem> mediaItems, + int startIndex, + long startPositionMs) + + + + + +protected ListenableFuture<?> +handleSetPlaybackParameters​(PlaybackParameters playbackParameters) + + + + + +protected ListenableFuture<?> +handleSetPlaylistMetadata​(MediaMetadata playlistMetadata) + + + + + protected ListenableFuture<?> handleSetPlayWhenReady​(boolean playWhenReady) - + - + +protected ListenableFuture<?> +handleSetRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode) + + + + + +protected ListenableFuture<?> +handleSetShuffleModeEnabled​(boolean shuffleModeEnabled) + + + + + +protected ListenableFuture<?> +handleSetTrackSelectionParameters​(TrackSelectionParameters trackSelectionParameters) + + + + + +protected ListenableFuture<?> +handleSetVideoOutput​(Object videoOutput) + +
      Handles calls to set the video output.
      + + + +protected ListenableFuture<?> +handleSetVolume​(float volume) + +
      Handles calls to Player.setVolume(float).
      + + + +protected ListenableFuture<?> +handleStop() + +
      Handles calls to Player.stop().
      + + + void increaseDeviceVolume()
      Increases the volume of the device.
      - + protected void invalidateState()
      Invalidates the current state.
      - + boolean isDeviceMuted()
      Gets whether the device is muted or not.
      - + boolean isLoading()
      Whether the player is currently loading the source.
      - + boolean isPlayingAd()
      Returns whether the player is currently playing an ad.
      - + void moveMediaItems​(int fromIndex, int toIndex, @@ -657,28 +836,28 @@ extends Moves the media item range to the new index. - + void prepare()
      Prepares the player.
      - + void release()
      Releases the player.
      - + void removeListener​(Player.Listener listener)
      Unregister a listener registered through Player.addListener(Listener).
      - + void removeMediaItems​(int fromIndex, int toIndex) @@ -686,95 +865,97 @@ extends Removes a range of media items from the playlist. - + void -seekTo​(int mediaItemIndex, - long positionMs) +seekTo​(int mediaItemIndex, + long positionMs, + @com.google.android.exoplayer2.Player.Command int seekCommand, + boolean isRepeatingCurrentItem) -
      Seeks to a position specified in milliseconds in the specified MediaItem.
      +
      Seeks to a position in the specified MediaItem.
      - + void setDeviceMuted​(boolean muted)
      Sets the mute state of the device.
      - + void setDeviceVolume​(int volume)
      Sets the volume of the device.
      - + void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition) -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      - + void setMediaItems​(List<MediaItem> mediaItems, int startIndex, long startPositionMs) -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      - + void setPlaybackParameters​(PlaybackParameters playbackParameters)
      Attempts to set the playback parameters.
      - + void setPlaylistMetadata​(MediaMetadata mediaMetadata)
      Sets the playlist MediaMetadata.
      - + void setPlayWhenReady​(boolean playWhenReady)
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY.
      - + void -setRepeatMode​(int repeatMode) +setRepeatMode​(@com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Sets the Player.RepeatMode to be used for playback.
      - + void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      Sets whether shuffling of media items is enabled.
      - + void setTrackSelectionParameters​(TrackSelectionParameters parameters)
      Sets the parameters constraining the track selection.
      - + void setVideoSurface​(Surface surface)
      Sets the Surface onto which video will be rendered.
      - + void setVideoSurfaceHolder​(SurfaceHolder surfaceHolder) @@ -782,21 +963,21 @@ extends - + void setVideoSurfaceView​(SurfaceView surfaceView)
      Sets the SurfaceView onto which video will be rendered.
      - + void setVideoTextureView​(TextureView textureView)
      Sets the TextureView onto which video will be rendered.
      - + void setVolume​(float volume) @@ -804,14 +985,14 @@ extends - + void stop()
      Stops playback without resetting the playlist.
      - + void stop​(boolean reset)   @@ -822,7 +1003,7 @@ extends

      Methods inherited from class com.google.android.exoplayer2.BasePlayer

      -addMediaItem, addMediaItem, addMediaItems, canAdvertiseSession, clearMediaItems, getBufferedPercentage, getContentDuration, getCurrentLiveOffset, getCurrentManifest, getCurrentMediaItem, getCurrentWindowIndex, getMediaItemAt, getMediaItemCount, getNextMediaItemIndex, getNextWindowIndex, getPreviousMediaItemIndex, getPreviousWindowIndex, hasNext, hasNextMediaItem, hasNextWindow, hasPrevious, hasPreviousMediaItem, hasPreviousWindow, isCommandAvailable, isCurrentMediaItemDynamic, isCurrentMediaItemLive, isCurrentMediaItemSeekable, isCurrentWindowDynamic, isCurrentWindowLive, isCurrentWindowSeekable, isPlaying, moveMediaItem, next, pause, play, previous, removeMediaItem, repeatCurrentMediaItem, seekBack, seekForward, seekTo, seekToDefaultPosition, seekToDefaultPosition, seekToNext, seekToNextMediaItem, seekToNextWindow, seekToPrevious, seekToPreviousMediaItem, seekToPreviousWindow, setMediaItem, setMediaItem, setMediaItem, setMediaItems, setPlaybackSpeed
    • +addMediaItem, addMediaItem, addMediaItems, canAdvertiseSession, clearMediaItems, getBufferedPercentage, getContentDuration, getCurrentLiveOffset, getCurrentManifest, getCurrentMediaItem, getCurrentWindowIndex, getMediaItemAt, getMediaItemCount, getNextMediaItemIndex, getNextWindowIndex, getPreviousMediaItemIndex, getPreviousWindowIndex, hasNext, hasNextMediaItem, hasNextWindow, hasPrevious, hasPreviousMediaItem, hasPreviousWindow, isCommandAvailable, isCurrentMediaItemDynamic, isCurrentMediaItemLive, isCurrentMediaItemSeekable, isCurrentWindowDynamic, isCurrentWindowLive, isCurrentWindowSeekable, isPlaying, moveMediaItem, next, pause, play, previous, removeMediaItem, seekBack, seekForward, seekTo, seekTo, seekToDefaultPosition, seekToDefaultPosition, seekToNext, seekToNextMediaItem, seekToNextWindow, seekToPrevious, seekToPreviousMediaItem, seekToPreviousWindow, setMediaItem, setMediaItem, setMediaItem, setMediaItems, setPlaybackSpeed
    • @@ -945,13 +1130,7 @@ extends Returns the player's currently available Player.Commands.

      The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands - change. - -

      Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a Player.getPlayerError() player error}. - -

      Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem. + change.

      Returns:
      The currently available Player.Commands.
      @@ -970,7 +1149,9 @@ extends Description copied from interface: Player
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. -

      If the player is already in the ready state then this method pauses and resumes playback.

      +

      If the player is already in the ready state then this method pauses and resumes playback. + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Parameters:
      playWhenReady - Whether playback should proceed when ready.
      @@ -1003,10 +1184,12 @@ extends public final void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
      @@ -1023,10 +1206,12 @@ extends -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -1044,12 +1229,14 @@ extends public final void addMediaItems​(int index, List<MediaItem> mediaItems)
      Description copied from interface: Player
      -
      Adds a list of media items at the given index of the playlist.
      +
      Adds a list of media items at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -1063,11 +1250,15 @@ extends -
      Moves the media item range to the new index.
      +
      Moves the media item range to the new index. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -1083,12 +1274,15 @@ extends public final void removeMediaItems​(int fromIndex, int toIndex) -
      Removes a range of media items from the playlist.
      +
      Removes a range of media items from the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -1102,6 +1296,8 @@ extends Description copied from interface: Player
      Prepares the player. +

      This method must only be called if Player.COMMAND_PREPARE is available. +

      This will move the player out of idle state and the player will start loading media and acquire resources needed for playback.

      @@ -1112,12 +1308,13 @@ extends
    • getPlaybackState

      -
      public final int getPlaybackState()
      +
      @State
      +public final @com.google.android.exoplayer2.Player.State int getPlaybackState()
      Description copied from interface: Player
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -1135,7 +1332,7 @@ extends , or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -1164,15 +1361,18 @@ public final  + @@ -1395,7 +1609,9 @@ public final public final Tracks getCurrentTracks()
      Description copied from interface: Player
      -
      Returns the current tracks.
      +
      Returns the current tracks. + +

      This method must only be called if Player.COMMAND_GET_TRACKS is available.

      See Also:
      Player.Listener.onTracksChanged(Tracks)
      @@ -1439,7 +1655,10 @@ public final Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS is + available.
    • @@ -1456,7 +1675,9 @@ public final MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, - it will be prioritised above the same field coming from static or timed metadata. + it will be prioritised above the same field coming from static or timed metadata. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available. @@ -1467,7 +1688,9 @@ public final public final MediaMetadata getPlaylistMetadata()

      Description copied from interface: Player
      -
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      +
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available.

      @@ -1478,7 +1701,9 @@ public final public final void setPlaylistMetadata​(MediaMetadata mediaMetadata)
      Description copied from interface: Player
      -
      Sets the playlist MediaMetadata.
      +
      Sets the playlist MediaMetadata. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEMS_METADATA is available.

      @@ -1489,7 +1714,9 @@ public final public final Timeline getCurrentTimeline()
      Description copied from interface: Player
      -
      Returns the current Timeline. Never null, but may be empty.
      +
      Returns the current Timeline. Never null, but may be empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      See Also:
      Player.Listener.onTimelineChanged(Timeline, int)
      @@ -1504,7 +1731,9 @@ public final public final int getCurrentPeriodIndex() -
      Returns the index of the period currently being played.
      +
      Returns the index of the period currently being played. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      @@ -1516,7 +1745,9 @@ public final public final int getCurrentMediaItemIndex()
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is - empty.
      + empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available. @@ -1528,7 +1759,9 @@ public final public final long getDuration()

      Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if - the duration is not known.
      + the duration is not known. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1540,7 +1773,9 @@ public final public final long getCurrentPosition()

      Returns the playback position in the current content or ad, in milliseconds, or the prospective - position in milliseconds if the current timeline is empty.
      + position in milliseconds if the current timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1552,7 +1787,9 @@ public final public final long getBufferedPosition()

      Returns an estimate of the position in the current content or ad up to which data is buffered, - in milliseconds.
      + in milliseconds. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1564,7 +1801,9 @@ public final public final long getTotalBufferedDuration()

      Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and media items.
      + This includes pre-buffered data for subsequent ads and media items. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1575,7 +1814,9 @@ public final public final boolean isPlayingAd()

      -
      Returns whether the player is currently playing an ad.
      +
      Returns whether the player is currently playing an ad. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      @@ -1587,7 +1828,9 @@ public final public final int getCurrentAdGroupIndex()
      If Player.isPlayingAd() returns true, returns the index of the ad group in the period - currently being played. Returns C.INDEX_UNSET otherwise.
      + currently being played. Returns C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1599,7 +1842,9 @@ public final public final int getCurrentAdIndexInAdGroup()

      If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns - C.INDEX_UNSET otherwise.
      + C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1612,7 +1857,9 @@ public final Description copied from interface: Player

      If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getCurrentPosition().
      + playing, the returned position is the same as that returned by Player.getCurrentPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1625,7 +1872,9 @@ public final Description copied from interface: Player

      If Player.isPlayingAd() returns true, returns an estimate of the content position in the current content up to which data is buffered, in milliseconds. If there is no ad playing, - the returned position is the same as that returned by Player.getBufferedPosition().
      + the returned position is the same as that returned by Player.getBufferedPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1636,7 +1885,9 @@ public final public final AudioAttributes getAudioAttributes()

      Description copied from interface: Player
      -
      Returns the attributes for audio playback.
      +
      Returns the attributes for audio playback. + +

      This method must only be called if Player.COMMAND_GET_AUDIO_ATTRIBUTES is available.

      @@ -1648,7 +1899,9 @@ public final public final void setVolume​(float volume)
      Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal - unchanged), inclusive.
      + unchanged), inclusive. + +

      This method must only be called if Player.COMMAND_SET_VOLUME is available.

      Parameters:
      volume - Linear output gain to apply to all audio channels.
      @@ -1663,42 +1916,15 @@ public final public final float getVolume() -
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      +
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + +

      This method must only be called if Player.COMMAND_GET_VOLUME is available.

      Returns:
      The linear gain applied to all audio channels.
      - - - - - - - -
        -
      • -

        clearVideoSurface

        -
        public final void clearVideoSurface​(@Nullable
        -                                    Surface surface)
        -
        Description copied from interface: Player
        -
        Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
        -
        -
        Parameters:
        -
        surface - The surface to clear.
        -
        -
      • -
      @@ -1714,7 +1940,9 @@ public final SurfaceView, TextureView or SurfaceHolder then it's recommended to use Player.setVideoSurfaceView(SurfaceView), Player.setVideoTextureView(TextureView) or Player.setVideoSurfaceHolder(SurfaceHolder) rather than this method, since passing the holder allows the player to track the lifecycle of the surface - automatically. + automatically. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surface - The Surface.
      @@ -1734,30 +1962,15 @@ public final SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceHolder - The surface holder.
      - - - -
        -
      • -

        clearVideoSurfaceHolder

        -
        public final void clearVideoSurfaceHolder​(@Nullable
        -                                          SurfaceHolder surfaceHolder)
        -
        Description copied from interface: Player
        -
        Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
        -
        -
        Parameters:
        -
        surfaceHolder - The surface holder to clear.
        -
        -
      • -
      @@ -1771,30 +1984,15 @@ public final SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceView - The surface view.
      - - - -
        -
      • -

        clearVideoSurfaceView

        -
        public final void clearVideoSurfaceView​(@Nullable
        -                                        SurfaceView surfaceView)
        -
        Description copied from interface: Player
        -
        Clears the SurfaceView onto which video is being rendered if it matches the one passed. - Else does nothing.
        -
        -
        Parameters:
        -
        surfaceView - The texture view to clear.
        -
        -
      • -
      @@ -1808,13 +2006,86 @@ public final TextureView.SurfaceTextureListener methods must be the - thread associated with Player.getApplicationLooper(). + thread associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view.
      + + + + + + + +
        +
      • +

        clearVideoSurface

        +
        public final void clearVideoSurface​(@Nullable
        +                                    Surface surface)
        +
        Description copied from interface: Player
        +
        Clears the Surface onto which video is being rendered if it matches the one passed. + Else does nothing. + +

        This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

        +
        +
        Parameters:
        +
        surface - The surface to clear.
        +
        +
      • +
      + + + +
        +
      • +

        clearVideoSurfaceHolder

        +
        public final void clearVideoSurfaceHolder​(@Nullable
        +                                          SurfaceHolder surfaceHolder)
        +
        Description copied from interface: Player
        +
        Clears the SurfaceHolder that holds the Surface onto which video is being + rendered if it matches the one passed. Else does nothing. + +

        This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

        +
        +
        Parameters:
        +
        surfaceHolder - The surface holder to clear.
        +
        +
      • +
      + + + +
        +
      • +

        clearVideoSurfaceView

        +
        public final void clearVideoSurfaceView​(@Nullable
        +                                        SurfaceView surfaceView)
        +
        Description copied from interface: Player
        +
        Clears the SurfaceView onto which video is being rendered if it matches the one passed. + Else does nothing. + +

        This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

        +
        +
        Parameters:
        +
        surfaceView - The texture view to clear.
        +
        +
      • +
      @@ -1825,7 +2096,9 @@ public final TextureView textureView)
      Description copied from interface: Player
      Clears the TextureView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view to clear.
      @@ -1873,7 +2146,9 @@ public final public final CueGroup getCurrentCues()
      Description copied from interface: Player
      -
      Returns the current CueGroup.
      +
      Returns the current CueGroup. + +

      This method must only be called if Player.COMMAND_GET_TEXT is available.

      @@ -1903,7 +2178,9 @@ public final Util.getStreamTypeForAudioUsage(int).

      For devices with remote playback, the volume of the - remote device is returned. + remote device is returned. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available. @@ -1914,7 +2191,9 @@ public final public final boolean isDeviceMuted()

      -
      Gets whether the device is muted or not.
      +
      Gets whether the device is muted or not. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available.

      @@ -1925,7 +2204,9 @@ public final public final void setDeviceVolume​(int volume) -
      Sets the volume of the device.
      +
      Sets the volume of the device. + +

      This method must only be called if Player.COMMAND_SET_DEVICE_VOLUME is available.

      Parameters:
      volume - The volume to set.
      @@ -1940,7 +2221,9 @@ public final public final void increaseDeviceVolume() -
      Increases the volume of the device.
      +
      Increases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      @@ -1951,7 +2234,9 @@ public final public final void decreaseDeviceVolume() -
      Decreases the volume of the device.
      +
      Decreases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      @@ -1962,7 +2247,9 @@ public final public final void setDeviceMuted​(boolean muted) -
      Sets the mute state of the device.
      +
      Sets the mute state of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      @@ -2024,15 +2311,37 @@ protected  + + + - @@ -1766,7 +1768,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      Deprecated.
      Description copied from interface: Player
      Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
      + currently set on the player. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      clearVideoSurface in interface ExoPlayer.VideoComponent
      @@ -1786,7 +1790,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      Deprecated.
      Description copied from interface: Player
      Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      clearVideoSurface in interface ExoPlayer.VideoComponent
      @@ -1813,7 +1819,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb

      If the surface is held by a SurfaceView, TextureView or SurfaceHolder then it's recommended to use Player.setVideoSurfaceView(SurfaceView), Player.setVideoTextureView(TextureView) or Player.setVideoSurfaceHolder(SurfaceHolder) rather than this method, since passing the holder allows the player to track the lifecycle of the surface - automatically. + automatically. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      setVideoSurface in interface ExoPlayer.VideoComponent
      @@ -1838,7 +1846,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb rendered. The player will track the lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      setVideoSurfaceHolder in interface ExoPlayer.VideoComponent
      @@ -1860,7 +1870,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      Deprecated.
      Description copied from interface: Player
      Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
      + rendered if it matches the one passed. Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      clearVideoSurfaceHolder in interface ExoPlayer.VideoComponent
      @@ -1885,7 +1897,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      setVideoSurfaceView in interface ExoPlayer.VideoComponent
      @@ -1907,7 +1921,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      Deprecated.
      Description copied from interface: Player
      Clears the SurfaceView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      clearVideoSurfaceView in interface ExoPlayer.VideoComponent
      @@ -1932,7 +1948,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb lifecycle of the surface automatically.

      The thread that calls the TextureView.SurfaceTextureListener methods must be the - thread associated with Player.getApplicationLooper(). + thread associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      setVideoTextureView in interface ExoPlayer.VideoComponent
      @@ -1954,7 +1972,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      Deprecated.
      Description copied from interface: Player
      Clears the TextureView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Specified by:
      clearVideoTextureView in interface ExoPlayer.VideoComponent
      @@ -1974,7 +1994,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      public void addAudioOffloadListener​(ExoPlayer.AudioOffloadListener listener)
      Deprecated.
      Description copied from interface: ExoPlayer
      -
      Adds a listener to receive audio offload events.
      +
      Adds a listener to receive audio offload events. + +

      This method can be called from any thread.

      Specified by:
      addAudioOffloadListener in interface ExoPlayer
      @@ -2045,7 +2067,9 @@ public @com.google.android.exoplayer2.C.VideoChangeFrameRateStrategy int&nb
      public AudioAttributes getAudioAttributes()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the attributes for audio playback.
      +
      Returns the attributes for audio playback. + +

      This method must only be called if Player.COMMAND_GET_AUDIO_ATTRIBUTES is available.

      Specified by:
      getAudioAttributes in interface ExoPlayer.AudioComponent
      @@ -2163,7 +2187,9 @@ public void setPreferredAudioDevice​(@Nullable
      Deprecated.
      Description copied from interface: Player
      Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal - unchanged), inclusive.
      + unchanged), inclusive. + +

      This method must only be called if Player.COMMAND_SET_VOLUME is available.

      Specified by:
      setVolume in interface ExoPlayer.AudioComponent
      @@ -2183,7 +2209,9 @@ public void setPreferredAudioDevice​(@Nullable
      public float getVolume()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      +
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + +

      This method must only be called if Player.COMMAND_GET_VOLUME is available.

      Specified by:
      getVolume in interface ExoPlayer.AudioComponent
      @@ -2257,7 +2285,9 @@ public void setPreferredAudioDevice​(@Nullable
      public void addAnalyticsListener​(AnalyticsListener listener)
      Deprecated.
      Description copied from interface: ExoPlayer
      -
      Adds an AnalyticsListener to receive analytics events.
      +
      Adds an AnalyticsListener to receive analytics events. + +

      This method can be called from any thread.

      Specified by:
      addAnalyticsListener in interface ExoPlayer
      @@ -2490,7 +2520,9 @@ public public CueGroup getCurrentCues()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the current CueGroup.
      +
      Returns the current CueGroup. + +

      This method must only be called if Player.COMMAND_GET_TEXT is available.

      Specified by:
      getCurrentCues in interface ExoPlayer.TextComponent
      @@ -2508,7 +2540,9 @@ public public Looper getPlaybackLooper()
      Deprecated.
      Description copied from interface: ExoPlayer
      -
      Returns the Looper associated with the playback thread.
      +
      Returns the Looper associated with the playback thread. + +

      This method may be called from any thread.

      Specified by:
      getPlaybackLooper in interface ExoPlayer
      @@ -2525,7 +2559,9 @@ public Deprecated.
      Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
      + player and on which player events are received. + +

      This method can be called from any thread.

      Specified by:
      getApplicationLooper in interface Player
      @@ -2541,7 +2577,9 @@ public public Clock getClock()
      Deprecated.
      Description copied from interface: ExoPlayer
      -
      Returns the Clock used for playback.
      +
      Returns the Clock used for playback. + +

      This method can be called from any thread.

      Specified by:
      getClock in interface ExoPlayer
      @@ -2559,7 +2597,9 @@ public Description copied from interface: Player
      Registers a listener to receive all events from the player. -

      The listener's methods will be called on the thread associated with Player.getApplicationLooper().

      +

      The listener's methods will be called on the thread associated with Player.getApplicationLooper(). + +

      This method can be called from any thread.

      Specified by:
      addListener in interface Player
      @@ -2597,12 +2637,12 @@ public Deprecated. -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      Specified by:
      getPlaybackState in interface Player
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -2624,7 +2664,7 @@ public @com.google.android.exoplayer2.Player.PlaybackSuppressionReason int&
      Specified by:
      getPlaybackSuppressionReason in interface Player
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -2683,13 +2723,7 @@ public void retry()
      Returns the player's currently available Player.Commands.

      The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands - change. - -

      Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a Player.getPlayerError() player error}. - -

      Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem.

      + change.
      Specified by:
      getAvailableCommands in interface Player
      @@ -2711,6 +2745,8 @@ public void retry()
      Description copied from interface: Player
      Prepares the player. +

      This method must only be called if Player.COMMAND_PREPARE is available. +

      This will move the player out of idle state and the player will start loading media and acquire resources needed for playback.

      @@ -2766,12 +2802,14 @@ public void prepare​(Deprecated. -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
      @@ -2789,12 +2827,14 @@ public void prepare​(Deprecated. -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      setMediaItems in interface Player
      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -2939,14 +2979,16 @@ public void prepare​(List<MediaItem> mediaItems)
      Deprecated.
      Description copied from interface: Player
      -
      Adds a list of media items at the given index of the playlist.
      +
      Adds a list of media items at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      addMediaItems in interface Player
      Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -3037,13 +3079,17 @@ public void prepare​(Deprecated. -
      Moves the media item range to the new index.
      +
      Moves the media item range to the new index. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      moveMediaItems in interface Player
      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -3060,14 +3106,17 @@ public void prepare​(Deprecated. -
      Removes a range of media items from the playlist.
      +
      Removes a range of media items from the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Specified by:
      removeMediaItems in interface Player
      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -3100,7 +3149,9 @@ public void prepare​(Description copied from interface: Player
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. -

      If the player is already in the ready state then this method pauses and resumes playback.

      +

      If the player is already in the ready state then this method pauses and resumes playback. + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Specified by:
      setPlayWhenReady in interface Player
      @@ -3198,7 +3249,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Deprecated.
      Description copied from interface: Player
      -
      Sets the Player.RepeatMode to be used for playback.
      +
      Sets the Player.RepeatMode to be used for playback. + +

      This method must only be called if Player.COMMAND_SET_REPEAT_MODE is available.

      Specified by:
      setRepeatMode in interface Player
      @@ -3216,7 +3269,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM
      public void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      Deprecated.
      Description copied from interface: Player
      -
      Sets whether shuffling of media items is enabled.
      +
      Sets whether shuffling of media items is enabled. + +

      This method must only be called if Player.COMMAND_SET_SHUFFLE_MODE is available.

      Specified by:
      setShuffleModeEnabled in interface Player
      @@ -3263,24 +3318,29 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM
      - +
      • seekTo

        public void seekTo​(int mediaItemIndex,
        -                   long positionMs)
        + long positionMs, + @Command + @com.google.android.exoplayer2.Player.Command int seekCommand, + boolean isRepeatingCurrentItem)
        Deprecated.
        -
        Description copied from interface: Player
        -
        Seeks to a position specified in milliseconds in the specified MediaItem.
        +
        Description copied from class: BasePlayer
        +
        Seeks to a position in the specified MediaItem.
        Specified by:
        -
        seekTo in interface Player
        +
        seekTo in class BasePlayer
        Parameters:
        mediaItemIndex - The index of the MediaItem.
        -
        positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET - to seek to the media item's default position.
        +
        positionMs - The seek position in the specified MediaItem in milliseconds, or + C.TIME_UNSET to seek to the media item's default position.
        +
        seekCommand - The Player.Command used to trigger the seek.
        +
        isRepeatingCurrentItem - Whether this seeks repeats the current item.
      @@ -3357,7 +3417,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM player to the default, which means there is no speed or pitch adjustment.

      Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently - active playback parameters change. + active playback parameters change. + +

      This method must only be called if Player.COMMAND_SET_SPEED_AND_PITCH is available.

      Specified by:
      setPlaybackParameters in interface Player
      @@ -3478,7 +3540,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM still be called on the player if it's no longer required.

      Calling this method does not clear the playlist, reset the playback position or the playback - error. + error. + +

      This method must only be called if Player.COMMAND_STOP is available.

      Specified by:
      stop in interface Player
      @@ -3668,7 +3732,9 @@ public public Tracks getCurrentTracks()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the current tracks.
      +
      Returns the current tracks. + +

      This method must only be called if Player.COMMAND_GET_TRACKS is available.

      Specified by:
      getCurrentTracks in interface Player
      @@ -3718,7 +3784,10 @@ public Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS is + available.
      Specified by:
      setTrackSelectionParameters in interface Player
      @@ -3740,7 +3809,9 @@ public MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, - it will be prioritised above the same field coming from static or timed metadata. + it will be prioritised above the same field coming from static or timed metadata. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available.

      Specified by:
      getMediaMetadata in interface Player
      @@ -3756,7 +3827,9 @@ public public MediaMetadata getPlaylistMetadata()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      +
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available.

      Specified by:
      getPlaylistMetadata in interface Player
      @@ -3772,7 +3845,9 @@ public public void setPlaylistMetadata​(MediaMetadata mediaMetadata)
      Deprecated.
      Description copied from interface: Player
      -
      Sets the playlist MediaMetadata.
      +
      Sets the playlist MediaMetadata. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEMS_METADATA is available.

      Specified by:
      setPlaylistMetadata in interface Player
      @@ -3788,7 +3863,9 @@ public public Timeline getCurrentTimeline()
      Deprecated.
      Description copied from interface: Player
      -
      Returns the current Timeline. Never null, but may be empty.
      +
      Returns the current Timeline. Never null, but may be empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getCurrentTimeline in interface Player
      @@ -3806,7 +3883,9 @@ public public int getCurrentPeriodIndex()
      Deprecated.
      -
      Returns the index of the period currently being played.
      +
      Returns the index of the period currently being played. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getCurrentPeriodIndex in interface Player
      @@ -3823,7 +3902,9 @@ public Deprecated.
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is - empty.
      + empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      Specified by:
      getCurrentMediaItemIndex in interface Player
      @@ -3840,7 +3921,9 @@ public Deprecated.
      Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if - the duration is not known.
      + the duration is not known. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getDuration in interface Player
      @@ -3857,7 +3940,9 @@ public Deprecated.
      Returns the playback position in the current content or ad, in milliseconds, or the prospective - position in milliseconds if the current timeline is empty.
      + position in milliseconds if the current timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getCurrentPosition in interface Player
      @@ -3874,7 +3959,9 @@ public Deprecated.
      Returns an estimate of the position in the current content or ad up to which data is buffered, - in milliseconds.
      + in milliseconds. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getBufferedPosition in interface Player
      @@ -3891,7 +3978,9 @@ public Deprecated.
      Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and media items.
      + This includes pre-buffered data for subsequent ads and media items. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getTotalBufferedDuration in interface Player
      @@ -3907,7 +3996,9 @@ public public boolean isPlayingAd()
      Deprecated.
      -
      Returns whether the player is currently playing an ad.
      +
      Returns whether the player is currently playing an ad. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      isPlayingAd in interface Player
      @@ -3924,7 +4015,9 @@ public Deprecated.
      If Player.isPlayingAd() returns true, returns the index of the ad group in the period - currently being played. Returns C.INDEX_UNSET otherwise.
      + currently being played. Returns C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getCurrentAdGroupIndex in interface Player
      @@ -3941,7 +4034,9 @@ public Deprecated.
      If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns - C.INDEX_UNSET otherwise.
      + C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getCurrentAdIndexInAdGroup in interface Player
      @@ -3959,7 +4054,9 @@ public Description copied from interface: Player
      If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getCurrentPosition().
      + playing, the returned position is the same as that returned by Player.getCurrentPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getContentPosition in interface Player
      @@ -3977,7 +4074,9 @@ public Description copied from interface: Player
      If Player.isPlayingAd() returns true, returns an estimate of the content position in the current content up to which data is buffered, in milliseconds. If there is no ad playing, - the returned position is the same as that returned by Player.getBufferedPosition().
      + the returned position is the same as that returned by Player.getBufferedPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      Specified by:
      getContentBufferedPosition in interface Player
      @@ -4063,7 +4162,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)Util.getStreamTypeForAudioUsage(int).

      For devices with remote playback, the volume of the - remote device is returned. + remote device is returned. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available.

      Specified by:
      getDeviceVolume in interface ExoPlayer.DeviceComponent
      @@ -4081,7 +4182,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)public boolean isDeviceMuted()
      Deprecated.
      Description copied from interface: Player
      -
      Gets whether the device is muted or not.
      +
      Gets whether the device is muted or not. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available.

      Specified by:
      isDeviceMuted in interface ExoPlayer.DeviceComponent
      @@ -4099,7 +4202,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)public void setDeviceVolume​(int volume)
      Deprecated.
      Description copied from interface: Player
      -
      Sets the volume of the device.
      +
      Sets the volume of the device. + +

      This method must only be called if Player.COMMAND_SET_DEVICE_VOLUME is available.

      Specified by:
      setDeviceVolume in interface ExoPlayer.DeviceComponent
      @@ -4119,7 +4224,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)public void increaseDeviceVolume()
      Deprecated.
      Description copied from interface: Player
      -
      Increases the volume of the device.
      +
      Increases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      Specified by:
      increaseDeviceVolume in interface ExoPlayer.DeviceComponent
      @@ -4137,7 +4244,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)public void decreaseDeviceVolume()
      Deprecated.
      Description copied from interface: Player
      -
      Decreases the volume of the device.
      +
      Decreases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      Specified by:
      decreaseDeviceVolume in interface ExoPlayer.DeviceComponent
      @@ -4155,7 +4264,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)public void setDeviceMuted​(boolean muted)
      Deprecated.
      Description copied from interface: Player
      -
      Sets the mute state of the device.
      +
      Sets the mute state of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      Specified by:
      setDeviceMuted in interface ExoPlayer.DeviceComponent
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/Timeline.RemotableTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/Timeline.RemotableTimeline.html index ec5f5723de..c256d1f57b 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/Timeline.RemotableTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/Timeline.RemotableTimeline.html @@ -317,7 +317,7 @@ extends T

      Methods inherited from class com.google.android.exoplayer2.Timeline

      -equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle +equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundleWithOneWindowOnly
      @@ -1746,7 +1775,7 @@ public void release()
      onPlayWhenReadyChanged in interface Player.Listener
      Parameters:
      playWhenReady - Whether playback will proceed when ready.
      -
      reason - The reason for the change.
      +
      reason - The Player.PlayWhenReadyChangeReason for the change.
      @@ -1828,7 +1857,7 @@ public void release()
      Specified by:
      onShuffleModeEnabledChanged in interface Player.Listener
      Parameters:
      -
      shuffleModeEnabled - Whether shuffling of media items is enabled.
      +
      shuffleModeEnabled - Whether shuffling of media items is enabled.
      @@ -1931,10 +1960,10 @@ public void release()

      onPlaybackParametersChanged

      public final void onPlaybackParametersChanged​(PlaybackParameters playbackParameters)
      Description copied from interface: Player.Listener
      -
      Called when the current playback parameters change. The playback parameters may change due to - a call to Player.setPlaybackParameters(PlaybackParameters), or the player itself may change - them (for example, if audio playback switches to passthrough or offload mode, where speed - adjustment is no longer possible). +
      Called when the value of Player.getPlaybackParameters() changes. The playback parameters + may change due to a call to Player.setPlaybackParameters(PlaybackParameters), or the player + itself may change them (for example, if audio playback switches to passthrough or offload + mode, where speed adjustment is no longer possible).

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2015,11 +2044,7 @@ public void release()

      onMediaMetadataChanged

      public void onMediaMetadataChanged​(MediaMetadata mediaMetadata)
      Description copied from interface: Player.Listener
      -
      Called when the combined MediaMetadata changes. - -

      The provided MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and - any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, it will be prioritised above the same field coming from static or - timed metadata. +

      Called when the value of Player.getMediaMetadata() changes.

      This method may be called multiple times in quick succession. @@ -2041,7 +2066,7 @@ public void release()

      onPlaylistMetadataChanged

      public void onPlaylistMetadataChanged​(MediaMetadata playlistMetadata)
      Description copied from interface: Player.Listener
      -
      Called when the playlist MediaMetadata changes. +
      Called when the value of Player.getPlaylistMetadata() changes.

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2079,10 +2104,10 @@ public void release()

      onCues

      public void onCues​(List<Cue> cues)
      Description copied from interface: Player.Listener
      -
      Called when there is a change in the Cues. +
      Called when the value of Player.getCurrentCues() changes. -

      Both Player.Listener.onCues(List) and Player.Listener.onCues(CueGroup) are called when there is a change - in the cues. You should only implement one or the other. +

      Both this method and Player.Listener.onCues(CueGroup) are called when there is a change in the + cues. You should only implement one or the other.

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2100,10 +2125,10 @@ public void release()

      onCues

      public void onCues​(CueGroup cueGroup)
      Description copied from interface: Player.Listener
      -
      Called when there is a change in the CueGroup. +
      Called when the value of Player.getCurrentCues() changes. -

      Both Player.Listener.onCues(List) and Player.Listener.onCues(CueGroup) are called when there is a change - in the cues. You should only implement one or the other. +

      Both this method and Player.Listener.onCues(List) are called when there is a change in the cues. + You should only implement one or the other.

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2174,7 +2199,7 @@ public void release()

      onAudioAttributesChanged

      public final void onAudioAttributesChanged​(AudioAttributes audioAttributes)
      Description copied from interface: Player.Listener
      -
      Called when the audio attributes change. +
      Called when the value of Player.getAudioAttributes() changes.

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2255,7 +2280,7 @@ public void release()
      public void onDeviceVolumeChanged​(int volume,
                                         boolean muted)
      Description copied from interface: Player.Listener
      -
      Called when the device volume or mute state changes. +
      Called when the value of Player.getDeviceVolume() or Player.isDeviceMuted() changes.

      Player.Listener.onEvents(Player, Events) will also be called to report this event along with other events that happen in the same Looper message queue iteration.

      @@ -2301,8 +2326,6 @@ public void release()

      State changes and events that happen within one Looper message queue iteration are reported together and only after all individual callbacks were triggered. -

      Only state changes represented by events are reported through this method. -

      Listeners should prefer this method over individual callbacks in the following cases:

        diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/Ac3Util.SyncFrameInfo.html b/docs/doc/reference/com/google/android/exoplayer2/audio/Ac3Util.SyncFrameInfo.html index d5e74d67f6..bbdfa5b227 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/Ac3Util.SyncFrameInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/audio/Ac3Util.SyncFrameInfo.html @@ -177,68 +177,75 @@ extends int +bitrate + +
        The bitrate of audio samples.
        + + + +int channelCount
        The number of audio channels
        - + int frameSize
        The size of the frame.
        - + String mimeType
        The sample mime type of the bitstream.
        - + int sampleCount
        Number of audio samples in the frame.
        - + int sampleRate
        The audio sampling rate in Hz.
        - + static int STREAM_TYPE_TYPE0
        Type 0 AC3 stream type.
        - + static int STREAM_TYPE_TYPE1
        Type 1 AC3 stream type.
        - + static int STREAM_TYPE_TYPE2
        Type 2 AC3 stream type.
        - + static int STREAM_TYPE_UNDEFINED
        Undefined AC3 stream type.
        - + @com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo.StreamType int streamType @@ -389,13 +396,23 @@ public final  -
          +
          • sampleCount

            public final int sampleCount
            Number of audio samples in the frame.
          + + + +
            +
          • +

            bitrate

            +
            public final int bitrate
            +
            The bitrate of audio samples.
            +
          • +
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/DefaultAudioSink.AudioTrackBufferSizeProvider.html b/docs/doc/reference/com/google/android/exoplayer2/audio/DefaultAudioSink.AudioTrackBufferSizeProvider.html index 72387deb5d..89c8cc4c93 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/DefaultAudioSink.AudioTrackBufferSizeProvider.html +++ b/docs/doc/reference/com/google/android/exoplayer2/audio/DefaultAudioSink.AudioTrackBufferSizeProvider.html @@ -178,11 +178,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); int -getBufferSizeInBytes​(int minBufferSizeInBytes, +getBufferSizeInBytes​(int minBufferSizeInBytes, @com.google.android.exoplayer2.C.Encoding int encoding, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int outputMode, int pcmFrameSize, int sampleRate, + int bitrate, double maxAudioTrackPlaybackSpeed)
        Returns the buffer size to use when creating an AudioTrack for a specific format and @@ -226,7 +227,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

        Method Detail

        - +
      @@ -507,13 +513,14 @@ implements Returns the buffer size for PCM playback.
      - +
      • getPassthroughBufferSizeInBytes

        -
        protected int getPassthroughBufferSizeInBytes​(@com.google.android.exoplayer2.C.Encoding int encoding)
        +
        protected int getPassthroughBufferSizeInBytes​(@com.google.android.exoplayer2.C.Encoding int encoding,
        +                                              int bitrate)
        Returns the buffer size for passthrough playback.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/audio/OpusUtil.html b/docs/doc/reference/com/google/android/exoplayer2/audio/OpusUtil.html index a194e04330..39a0292b8c 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/audio/OpusUtil.html +++ b/docs/doc/reference/com/google/android/exoplayer2/audio/OpusUtil.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9}; +var data = {"i0":9,"i1":9,"i2":9,"i3":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -154,6 +154,13 @@ extends static int +MAX_BYTES_PER_SECOND + +
      Maximum achievable Opus bitrate.
      + + + +static int SAMPLE_RATE
      Opus streams are always 48000 Hz.
      @@ -191,6 +198,20 @@ extends Parses the channel count from an Opus Identification Header.
      + +static long +getPacketDurationUs​(byte[] buffer) + +
      Returns the duration of the given audio packet.
      + + + +static int +parsePacketAudioSampleCount​(ByteBuffer buffer) + +
      Returns the number of audio samples in the given audio packet.
      + +
      @@ -1025,7 +1029,9 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      Registers a listener to receive all events from the player. -

      The listener's methods will be called on the thread associated with Player.getApplicationLooper().

      +

      The listener's methods will be called on the thread associated with Player.getApplicationLooper(). + +

      This method can be called from any thread.

      Parameters:
      listener - The listener to register.
      @@ -1057,10 +1063,12 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      public void setMediaItems​(List<MediaItem> mediaItems,
                                 boolean resetPosition)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
      @@ -1077,10 +1085,12 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n int startIndex, long startPositionMs)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -1098,12 +1108,14 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      public void addMediaItems​(int index,
                                 List<MediaItem> mediaItems)
      Description copied from interface: Player
      -
      Adds a list of media items at the given index of the playlist.
      +
      Adds a list of media items at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -1117,11 +1129,15 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n int toIndex, int newIndex)
      Description copied from interface: Player
      -
      Moves the media item range to the new index.
      +
      Moves the media item range to the new index. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -1137,12 +1153,15 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      public void removeMediaItems​(int fromIndex,
                                    int toIndex)
      Description copied from interface: Player
      -
      Removes a range of media items from the playlist.
      +
      Removes a range of media items from the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -1157,13 +1176,7 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      Returns the player's currently available Player.Commands.

      The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands - change. - -

      Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a Player.getPlayerError() player error}. - -

      Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem.

      + change.
      Returns:
      The currently available Player.Commands.
      @@ -1182,6 +1195,8 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      Description copied from interface: Player
      Prepares the player. +

      This method must only be called if Player.COMMAND_PREPARE is available. +

      This will move the player out of idle state and the player will start loading media and acquire resources needed for playback.

      @@ -1195,10 +1210,10 @@ public com.google.android.gms.cast.MediaQueueItem getItem​(int&n
      @State
       public @com.google.android.exoplayer2.Player.State int getPlaybackState()
      Description copied from interface: Player
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -1217,7 +1232,7 @@ public @com.google.android.exoplayer2.Player.PlaybackSuppressionReason int& true
      , or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -1256,7 +1271,9 @@ public Description copied from interface: Player
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. -

      If the player is already in the ready state then this method pauses and resumes playback.

      +

      If the player is already in the ready state then this method pauses and resumes playback. + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Parameters:
      playWhenReady - Whether playback should proceed when ready.
      @@ -1280,21 +1297,28 @@ public  +
      • seekTo

        public void seekTo​(int mediaItemIndex,
        -                   long positionMs)
        -
        Description copied from interface: Player
        -
        Seeks to a position specified in milliseconds in the specified MediaItem.
        + long positionMs, + @Command + @com.google.android.exoplayer2.Player.Command int seekCommand, + boolean isRepeatingCurrentItem) +
        Description copied from class: BasePlayer
        +
        Seeks to a position in the specified MediaItem.
        +
        Specified by:
        +
        seekTo in class BasePlayer
        Parameters:
        mediaItemIndex - The index of the MediaItem.
        -
        positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET - to seek to the media item's default position.
        +
        positionMs - The seek position in the specified MediaItem in milliseconds, or + C.TIME_UNSET to seek to the media item's default position.
        +
        seekCommand - The Player.Command used to trigger the seek.
        +
        isRepeatingCurrentItem - Whether this seeks repeats the current item.
      @@ -1381,7 +1405,9 @@ public Player.COMMAND_STOP is available. @@ -1423,7 +1449,9 @@ public void stop​(boolean reset) player to the default, which means there is no speed or pitch adjustment.

      Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently - active playback parameters change. + active playback parameters change. + +

      This method must only be called if Player.COMMAND_SET_SPEED_AND_PITCH is available.

      Parameters:
      playbackParameters - The playback parameters.
      @@ -1439,7 +1467,9 @@ public void stop​(boolean reset)
      public void setRepeatMode​(@RepeatMode
                                 @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Description copied from interface: Player
      -
      Sets the Player.RepeatMode to be used for playback.
      +
      Sets the Player.RepeatMode to be used for playback. + +

      This method must only be called if Player.COMMAND_SET_REPEAT_MODE is available.

      Parameters:
      repeatMode - The repeat mode.
      @@ -1472,7 +1502,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      setShuffleModeEnabled

      public void setShuffleModeEnabled​(boolean shuffleModeEnabled)
      Description copied from interface: Player
      -
      Sets whether shuffling of media items is enabled.
      +
      Sets whether shuffling of media items is enabled. + +

      This method must only be called if Player.COMMAND_SET_SHUFFLE_MODE is available.

      Parameters:
      shuffleModeEnabled - Whether shuffling is enabled.
      @@ -1502,7 +1534,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      getCurrentTracks

      public Tracks getCurrentTracks()
      Description copied from interface: Player
      -
      Returns the current tracks.
      +
      Returns the current tracks. + +

      This method must only be called if Player.COMMAND_GET_TRACKS is available.

      See Also:
      Player.Listener.onTracksChanged(Tracks)
      @@ -1546,7 +1580,10 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM .buildUpon() .setMaxVideoSizeSd() .build()) -
      + + +

      This method must only be called if Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS is + available. @@ -1563,7 +1600,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      This MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, - it will be prioritised above the same field coming from static or timed metadata. + it will be prioritised above the same field coming from static or timed metadata. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available. @@ -1583,7 +1622,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      getPlaylistMetadata

      public MediaMetadata getPlaylistMetadata()
      Description copied from interface: Player
      -
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      +
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available.

      @@ -1604,7 +1645,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      getCurrentTimeline

      public Timeline getCurrentTimeline()
      Description copied from interface: Player
      -
      Returns the current Timeline. Never null, but may be empty.
      +
      Returns the current Timeline. Never null, but may be empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      See Also:
      Player.Listener.onTimelineChanged(Timeline, int)
      @@ -1619,7 +1662,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      getCurrentPeriodIndex

      public int getCurrentPeriodIndex()
      Description copied from interface: Player
      -
      Returns the index of the period currently being played.
      +
      Returns the index of the period currently being played. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      @@ -1631,7 +1676,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM
      public int getCurrentMediaItemIndex()
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is - empty.
      + empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available. @@ -1643,7 +1690,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      public long getDuration()
      Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if - the duration is not known.
      + the duration is not known. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1655,7 +1704,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      public long getCurrentPosition()
      Returns the playback position in the current content or ad, in milliseconds, or the prospective - position in milliseconds if the current timeline is empty.
      + position in milliseconds if the current timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1667,7 +1718,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      public long getBufferedPosition()
      Returns an estimate of the position in the current content or ad up to which data is buffered, - in milliseconds.
      + in milliseconds. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1679,7 +1732,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      public long getTotalBufferedDuration()
      Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and media items.
      + This includes pre-buffered data for subsequent ads and media items. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1690,7 +1745,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      isPlayingAd

      public boolean isPlayingAd()
      -
      Returns whether the player is currently playing an ad.
      +
      Returns whether the player is currently playing an ad. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      @@ -1702,7 +1759,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM
      public int getCurrentAdGroupIndex()
      If Player.isPlayingAd() returns true, returns the index of the ad group in the period - currently being played. Returns C.INDEX_UNSET otherwise.
      + currently being played. Returns C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1714,7 +1773,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      public int getCurrentAdIndexInAdGroup()
      If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns - C.INDEX_UNSET otherwise.
      + C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1744,7 +1805,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getCurrentPosition().
      + playing, the returned position is the same as that returned by Player.getCurrentPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1757,7 +1820,9 @@ public @com.google.android.exoplayer2.Player.RepeatMode int getRepeatM

      If Player.isPlayingAd() returns true, returns an estimate of the content position in the current content up to which data is buffered, in milliseconds. If there is no ad playing, - the returned position is the same as that returned by Player.getBufferedPosition().
      + the returned position is the same as that returned by Player.getBufferedPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.html index 2f89d4eebe..4bf95e9279 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -217,6 +217,13 @@ extends ImaServerSideAdInsertionMediaSource.AdsLoader.Builder +setFocusSkipButtonWhenAvailable​(boolean focusSkipButtonWhenAvailable) + +

      Sets whether to focus the skip button (when available) on Android TV devices.
      + + + +ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setImaSdkSettings​(com.google.ads.interactivemedia.v3.api.ImaSdkSettings imaSdkSettings)
      Sets the IMA SDK settings.
      @@ -282,7 +289,7 @@ extends ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setImaSdkSettings​(com.google.ads.interactivemedia.v3.api.ImaSdkSettings imaSdkSettings)
      Sets the IMA SDK settings. -

      If this method is not called the default settings will be used.

      +

      If this method is not called, the default settings will be used with the language set to the preferred system language.

      Parameters:
      imaSdkSettings - The ImaSdkSettings.
      @@ -363,6 +370,27 @@ public  + + + +
        +
      • +

        setFocusSkipButtonWhenAvailable

        +
        @CanIgnoreReturnValue
        +public ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setFocusSkipButtonWhenAvailable​(boolean focusSkipButtonWhenAvailable)
        +
        Sets whether to focus the skip button (when available) on Android TV devices. The default + setting is true.
        +
        +
        Parameters:
        +
        focusSkipButtonWhenAvailable - Whether to focus the skip button (when available) on + Android TV devices.
        +
        Returns:
        +
        This builder, for convenience.
        +
        See Also:
        +
        AdsRenderingSettings.setFocusSkipButtonWhenAvailable(boolean)
        +
        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.html index f9b4798b6f..c8618578df 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.AdsLoader.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10}; +var data = {"i0":10,"i1":10,"i2":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -189,13 +189,20 @@ extends Description +void +focusSkipButton() + +
      Puts the focus on the skip button, if a skip button is present and an ad is playing.
      + + + ImaServerSideAdInsertionMediaSource.AdsLoader.State release()
      Releases resources.
      - + void setPlayer​(Player player) @@ -239,6 +246,20 @@ extends + + + +
        +
      • +

        focusSkipButton

        +
        public void focusSkipButton()
        +
        Puts the focus on the skip button, if a skip button is present and an ad is playing.
        +
        +
        See Also:
        +
        BaseManager.focus()
        +
        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.html b/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.html index f2bbfacf97..1b9de85028 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.html @@ -256,7 +256,7 @@ implements void -seekTo​(long positionMs) +seekTo​(long positionInMs)   @@ -268,7 +268,7 @@ implements void -setProgressUpdatingEnabled​(boolean enabled) +setProgressUpdatingEnabled​(boolean enable)   @@ -379,7 +379,7 @@ implements
    • setProgressUpdatingEnabled

      -
      public void setProgressUpdatingEnabled​(boolean enabled)
      +
      public void setProgressUpdatingEnabled​(boolean enable)
      Overrides:
      setProgressUpdatingEnabled in class androidx.leanback.media.PlayerAdapter
      @@ -457,7 +457,7 @@ implements
    • seekTo

      -
      public void seekTo​(long positionMs)
      +
      public void seekTo​(long positionInMs)
      Overrides:
      seekTo in class androidx.leanback.media.PlayerAdapter
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.html b/docs/doc/reference/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.html index f8ba2d6d9e..df95e64bed 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.html +++ b/docs/doc/reference/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -326,6 +326,14 @@ implements
      +DefaultExtractorsFactory +setTsSubtitleFormats​(List<Format> subtitleFormats) + +
      Sets a list of subtitle formats to pass to the DefaultTsPayloadReaderFactory used by + TsExtractor instances created by the factory.
      + +
      • @@ -594,6 +602,26 @@ public  + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.html b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.html index a374c74e8f..b503cd1e93 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":42,"i10":10,"i11":9,"i12":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":42,"i11":10,"i12":9,"i13":10}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -302,19 +302,27 @@ extends boolean -isFormatSupported​(Format format) +isFormatFunctionallySupported​(Format format) -
        Returns whether the decoder may support decoding the given format.
        +
        Returns whether the decoder may functionally support decoding the given format.
        boolean +isFormatSupported​(Format format) + +
        Returns whether the decoder may support decoding the given format both functionally and + performantly.
        + + + +boolean isHdr10PlusOutOfBandMetadataSupported()
        Whether the codec handles HDR10+ out-of-band metadata.
        - + boolean isSeamlessAdaptationSupported​(Format format) @@ -322,7 +330,7 @@ extends format. - + boolean isSeamlessAdaptationSupported​(Format oldFormat, Format newFormat, @@ -333,7 +341,7 @@ extends - + boolean isVideoSizeAndRateSupportedV21​(int width, int height, @@ -342,7 +350,7 @@ extends Whether the decoder supports video with a given width, height and frame rate. - + static MediaCodecInfo newInstance​(String name, String mimeType, @@ -357,7 +365,7 @@ extends Creates an instance. - + String toString()   @@ -647,7 +655,8 @@ public final public boolean isFormatSupported​(Format format) throws MediaCodecUtil.DecoderQueryException -
        Returns whether the decoder may support decoding the given format.
        +
        Returns whether the decoder may support decoding the given format both functionally and + performantly.
        Parameters:
        format - The input media format.
        @@ -658,6 +667,22 @@ public final  + + +
          +
        • +

          isFormatFunctionallySupported

          +
          public boolean isFormatFunctionallySupported​(Format format)
          +
          Returns whether the decoder may functionally support decoding the given format.
          +
          +
          Parameters:
          +
          format - The input media format.
          +
          Returns:
          +
          Whether the decoder may functionally support decoding the given format.
          +
          +
        • +
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.html b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.html index e3a63d8a21..3f5cc43057 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.html +++ b/docs/doc/reference/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.html @@ -221,8 +221,8 @@ extends getDecoderInfosSortedByFormatSupport​(List<MediaCodecInfo> decoderInfos, Format format) -
        Returns a copy of the provided decoder list sorted such that decoders with format support are - listed first.
        +
        Returns a copy of the provided decoder list sorted such that decoders with functional format + support are listed first.
        @@ -383,8 +383,8 @@ public static @CheckResult public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport​(List<MediaCodecInfo> decoderInfos, Format format) -
        Returns a copy of the provided decoder list sorted such that decoders with format support are - listed first. The returned list is modifiable for convenience.
        +
        Returns a copy of the provided decoder list sorted such that decoders with functional format + support are listed first. The returned list is modifiable for convenience.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html index 2ede7c4667..4dab123364 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html @@ -209,7 +209,8 @@ extends default void populateMediaMetadata​(MediaMetadata.Builder builder) -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -266,10 +267,8 @@ default byte[] getWrappedMetadataBytes()
    • populateMediaMetadata

      default void populateMediaMetadata​(MediaMetadata.Builder builder)
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Parameters:
      builder - The builder to be updated.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/PictureFrame.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/PictureFrame.html index bcd47cffc6..749def51f2 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/PictureFrame.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/PictureFrame.html @@ -314,7 +314,8 @@ implements void populateMediaMetadata​(MediaMetadata.Builder builder) -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -492,10 +493,8 @@ implements public void populateMediaMetadata​(MediaMetadata.Builder builder)
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Specified by:
      populateMediaMetadata in interface Metadata.Entry
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/VorbisComment.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/VorbisComment.html index a1c7e1bb86..d61f46d926 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/VorbisComment.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/flac/VorbisComment.html @@ -288,7 +288,8 @@ implements populateMediaMetadata​(MediaMetadata.Builder builder)
      Deprecated.
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -424,10 +425,8 @@ implements public void populateMediaMetadata​(MediaMetadata.Builder builder)
      Deprecated.
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Specified by:
      populateMediaMetadata in interface Metadata.Entry
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyHeaders.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyHeaders.html index 2318611071..6a379855f4 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyHeaders.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyHeaders.html @@ -311,7 +311,8 @@ implements void populateMediaMetadata​(MediaMetadata.Builder builder) -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -528,10 +529,8 @@ public static public void populateMediaMetadata​(MediaMetadata.Builder builder)
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Specified by:
      populateMediaMetadata in interface Metadata.Entry
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyInfo.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyInfo.html index 1ed9c27b0d..51bc7e8928 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/icy/IcyInfo.html @@ -270,7 +270,8 @@ implements void populateMediaMetadata​(MediaMetadata.Builder builder) -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -405,10 +406,8 @@ public final public void populateMediaMetadata​(MediaMetadata.Builder builder)
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Specified by:
      populateMediaMetadata in interface Metadata.Entry
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/ApicFrame.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/ApicFrame.html index 2edfa165fe..7d14e3d8a0 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/ApicFrame.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/ApicFrame.html @@ -278,7 +278,8 @@ extends void populateMediaMetadata​(MediaMetadata.Builder builder) -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      @@ -430,10 +431,8 @@ public final public void populateMediaMetadata​(MediaMetadata.Builder builder)
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      Parameters:
      builder - The builder to be updated.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.html b/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.html index 8976cbe5e8..6fcc9f1adf 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.html +++ b/docs/doc/reference/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.html @@ -191,7 +191,18 @@ extends String value -  + +
      Deprecated. +
      Use the first element of values instead.
      +
      + + + +ImmutableList<String> +values + +
      The text values of this frame.
      + + + + +
        +
      • +

        values

        +
        public final ImmutableList<String> values
        +
        The text values of this frame. Will always have at least one element.
      @@ -352,16 +389,35 @@ public final  + + +
        +
      • +

        TextInformationFrame

        +
        public TextInformationFrame​(String id,
        +                            @Nullable
        +                            String description,
        +                            List<String> values)
        +
      • +
      • TextInformationFrame

        -
        public TextInformationFrame​(String id,
        +
        @Deprecated
        +@InlineMe(replacement="this(id, description, ImmutableList.of(value))",
        +          imports="com.google.common.collect.ImmutableList")
        +public TextInformationFrame​(String id,
                                     @Nullable
                                     String description,
                                     String value)
        +
        Deprecated. +
        Use TextInformationFrame(String id, String description, String[] values + instead
        +
    • @@ -381,11 +437,8 @@ public final 

      populateMediaMetadata

      public void populateMediaMetadata​(MediaMetadata.Builder builder)
      -
      Description copied from interface: Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry. - -

      The order of the Metadata.Entry objects in the Metadata matters. If two Metadata.Entry entries attempt to populate the same MediaMetadata field, then the last one in - the list is used.

      +
      Uses the first element in values to set the relevant field in MediaMetadata + (as determined by Id3Frame.id).
      Parameters:
      builder - The builder to be updated.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/offline/DownloadService.html b/docs/doc/reference/com/google/android/exoplayer2/offline/DownloadService.html index 9ee6559c24..49bbe372cc 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/offline/DownloadService.html +++ b/docs/doc/reference/com/google/android/exoplayer2/offline/DownloadService.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":6,"i9":6,"i10":6,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9,"i25":9,"i26":9}; +var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":6,"i10":6,"i11":6,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9,"i25":9,"i26":9,"i27":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -150,7 +150,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      public abstract class DownloadService
       extends Service
      -
      A Service for downloading media.
      +
      A Service for downloading media. + +

      Apps with target SDK 33 and greater need to add the + android.permission.POST_NOTIFICATIONS permission to the manifest and request the permission at + runtime before starting downloads. Without that permission granted by the user, notifications + posted by this service are not displayed. See the + official UI guide for more detailed information.

    • @@ -453,13 +459,21 @@ extends +static void +clearDownloadManagerHelpers() + +
      Clear all download manager helpers before restarting the + service.
      + + + protected abstract DownloadManager getDownloadManager()
      Returns a DownloadManager to be used to downloaded content.
      - + protected abstract Notification getForegroundNotification​(List<Download> downloads, @com.google.android.exoplayer2.scheduler.Requirements.RequirementFlags int notMetRequirements) @@ -467,7 +481,7 @@ extends Returns a notification to be displayed when this service running in the foreground. - + protected abstract Scheduler getScheduler() @@ -475,43 +489,43 @@ extends + protected void invalidateForegroundNotification()
      Invalidates the current foreground notification and causes getForegroundNotification(List, int) to be invoked again if the service isn't stopped.
      - + IBinder onBind​(Intent intent)
      Throws UnsupportedOperationException because this service is not designed to be bound.
      - + void onCreate()   - + void onDestroy()   - + int onStartCommand​(Intent intent, int flags, int startId)   - + void onTaskRemoved​(Intent rootIntent)   - + static void sendAddDownload​(Context context, Class<? extends DownloadService> clazz, @@ -521,7 +535,7 @@ extends Starts the service if not started already and adds a new download. - + static void sendAddDownload​(Context context, Class<? extends DownloadService> clazz, @@ -532,7 +546,7 @@ extends Starts the service if not started already and adds a new download. - + static void sendPauseDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -541,7 +555,7 @@ extends Starts the service if not started already and pauses all downloads. - + static void sendRemoveAllDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -550,7 +564,7 @@ extends Starts the service if not started already and removes all downloads. - + static void sendRemoveDownload​(Context context, Class<? extends DownloadService> clazz, @@ -560,7 +574,7 @@ extends Starts the service if not started already and removes a download. - + static void sendResumeDownloads​(Context context, Class<? extends DownloadService> clazz, @@ -569,7 +583,7 @@ extends Starts the service if not started already and resumes all downloads. - + static void sendSetRequirements​(Context context, Class<? extends DownloadService> clazz, @@ -580,7 +594,7 @@ extends + static void sendSetStopReason​(Context context, Class<? extends DownloadService> clazz, @@ -591,7 +605,7 @@ extends Starts the service if not started already and sets the stop reason for one or all downloads. - + static void start​(Context context, Class<? extends DownloadService> clazz) @@ -599,7 +613,7 @@ extends Starts a download service to resume any ongoing downloads. - + static void startForeground​(Context context, Class<? extends DownloadService> clazz) @@ -1395,6 +1409,20 @@ protected DownloadService​(int foregroundNotificationId,
    • + + + +
        +
      • +

        clearDownloadManagerHelpers

        +
        public static void clearDownloadManagerHelpers()
        +
        Clear all download manager helpers before restarting the + service. + +

        Calling this method is normally only required if an app supports downloading content for + multiple users for which different download directories should be used.

        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/package-summary.html index 7625ae100a..0eb80365ff 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/package-summary.html @@ -180,7 +180,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Player.Listener -
      Listener of all changes in the Player.
      +
      Listener for changes in a Player.
      @@ -219,6 +219,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Builds Renderer instances for use by an ExoPlayer.
      + +SimpleBasePlayer.PositionSupplier + +
      A supplier for a position.
      + + @@ -494,7 +500,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Player.Commands -
      A set of commands.
      +
      A set of commands.
      @@ -506,7 +512,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Player.Events -
      A set of events.
      +
      A set of events.
      @@ -547,6 +553,32 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +SimpleBasePlayer.MediaItemData + +
      An immutable description of an item in the playlist, containing both static setup information + like MediaItem and dynamic data that is generally read from the media like the + duration.
      + + + +SimpleBasePlayer.MediaItemData.Builder + +
      A builder for SimpleBasePlayer.MediaItemData objects.
      + + + +SimpleBasePlayer.PeriodData + +
      Data describing the properties of a period inside a SimpleBasePlayer.MediaItemData.
      + + + +SimpleBasePlayer.PeriodData.Builder + +
      A builder for SimpleBasePlayer.PeriodData objects.
      + + + SimpleBasePlayer.State
      An immutable state description of the player.
      @@ -871,114 +903,121 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +MediaMetadata.MediaType + +
      The type of content described by the media item.
      + + + MediaMetadata.PictureType
      The picture type of the artwork.
      - + PlaybackException.ErrorCode
      Codes that identify causes of player errors.
      - + Player.Command -
      Commands that can be executed on a Player.
      +
      Commands that indicate which method calls are currently permitted on a particular + Player instance.
      - + Player.DiscontinuityReason
      Reasons for position discontinuities.
      - + Player.Event
      Events that can be reported via Player.Listener.onEvents(Player, Events).
      - + Player.MediaItemTransitionReason
      Reasons for media item transitions.
      - + Player.PlaybackSuppressionReason
      Reason why playback is suppressed even though Player.getPlayWhenReady() is true.
      - + Player.PlayWhenReadyChangeReason
      Reasons for playWhenReady changes.
      - + Player.RepeatMode
      Repeat modes for playback.
      - + Player.State
      Playback state.
      - + Player.TimelineChangeReason
      Reasons for timeline changes.
      - + Renderer.MessageType
      Represents a type of message that can be passed to a renderer.
      - + Renderer.State
      The renderer states.
      - + RendererCapabilities.AdaptiveSupport
      Level of renderer support for adaptive format switches.
      - + RendererCapabilities.Capabilities
      Combined renderer capabilities.
      - + RendererCapabilities.DecoderSupport
      Level of decoder support.
      - + RendererCapabilities.FormatSupport Deprecated.
      Use C.FormatSupport instead.
      - + RendererCapabilities.HardwareAccelerationSupport
      Level of renderer support for hardware acceleration.
      - + RendererCapabilities.TunnelingSupport
      Level of renderer support for tunneling.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/package-tree.html index d7a6f55a64..0c662ff3d8 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/package-tree.html @@ -179,6 +179,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.RendererConfiguration
    • com.google.android.exoplayer2.SeekParameters
    • +
    • com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
    • +
    • com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
    • +
    • com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
    • +
    • com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
    • com.google.android.exoplayer2.SimpleBasePlayer.State
    • com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
    • com.google.android.exoplayer2.SimpleExoPlayer.Builder
    • @@ -251,6 +255,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.Renderer.WakeupListener
    • com.google.android.exoplayer2.RendererCapabilities
    • com.google.android.exoplayer2.RenderersFactory
    • +
    • com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
    • @@ -289,6 +294,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.ExoPlaybackException.Type (implements java.lang.annotation.Annotation)
    • com.google.android.exoplayer2.ExoTimeoutException.TimeoutOperation (implements java.lang.annotation.Annotation)
    • com.google.android.exoplayer2.MediaMetadata.FolderType (implements java.lang.annotation.Annotation)
    • +
    • com.google.android.exoplayer2.MediaMetadata.MediaType (implements java.lang.annotation.Annotation)
    • com.google.android.exoplayer2.MediaMetadata.PictureType (implements java.lang.annotation.Annotation)
    • com.google.android.exoplayer2.PlaybackException.ErrorCode (implements java.lang.annotation.Annotation)
    • com.google.android.exoplayer2.Player.Command (implements java.lang.annotation.Annotation)
    • diff --git a/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html b/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html index c88da58678..d1685c9695 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html +++ b/docs/doc/reference/com/google/android/exoplayer2/robolectric/TestPlayerRunHelper.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9}; +var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -182,13 +182,22 @@ extends static void +runUntilIsLoading​(Player player, + boolean expectedIsLoading) + +
      Runs tasks of the main Looper until Player.isLoading() matches the expected + value or a playback error occurs.
      + + + +static void runUntilPendingCommandsAreFullyHandled​(ExoPlayer player)
      Runs tasks of the main Looper until the player completely handled all previously issued commands on the internal playback thread.
      - + static void runUntilPlaybackState​(Player player, @com.google.android.exoplayer2.Player.State int expectedState) @@ -197,7 +206,7 @@ extends - + static void runUntilPlayWhenReady​(Player player, boolean expectedPlayWhenReady) @@ -206,7 +215,7 @@ extends - + static void runUntilPositionDiscontinuity​(Player player, @com.google.android.exoplayer2.Player.DiscontinuityReason int expectedReason) @@ -215,7 +224,7 @@ extends Player.DiscontinuityReason or a playback error occurs. - + static void runUntilRenderedFirstFrame​(ExoPlayer player) @@ -223,7 +232,7 @@ extends - + static void runUntilSleepingForOffload​(ExoPlayer player, boolean expectedSleepForOffload) @@ -232,14 +241,14 @@ extends - + static Timeline runUntilTimelineChanged​(Player player)
      Runs tasks of the main Looper until a timeline change or a playback error occurs.
      - + static void runUntilTimelineChanged​(Player player, Timeline expectedTimeline) @@ -319,6 +328,29 @@ extends + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/CompositeMediaSource.html b/docs/doc/reference/com/google/android/exoplayer2/source/CompositeMediaSource.html index d01ceed690..b4141f8f50 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/CompositeMediaSource.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/CompositeMediaSource.html @@ -143,7 +143,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Direct Known Subclasses:
      -
      AdsMediaSource, ConcatenatingMediaSource, ImaServerSideAdInsertionMediaSource, MergingMediaSource, WrappingMediaSource
      +
      AdsMediaSource, ConcatenatingMediaSource, ConcatenatingMediaSource2, ImaServerSideAdInsertionMediaSource, MergingMediaSource, WrappingMediaSource

      public abstract class CompositeMediaSource<T>
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html
      index fb9ee7976c..43d998e574 100644
      --- a/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html
      +++ b/docs/doc/reference/com/google/android/exoplayer2/source/ForwardingTimeline.html
      @@ -328,7 +328,7 @@ extends Timeline
      -equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle
      +equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundleWithOneWindowOnly
       
       
       
       
       
      + + + +
        +
      • +

        parseTileCountFromProperties

        +
        @Nullable
        +protected Pair<Integer,​Integer> parseTileCountFromProperties​(List<Descriptor> essentialProperties)
        +
        Parses given descriptors for thumbnail tile information.
        +
        +
        Parameters:
        +
        essentialProperties - List of descriptors that contain thumbnail tile information.
        +
        Returns:
        +
        A pair of Integer values, where the first is the count of horizontal tiles and the + second is the count of vertical tiles, or null if no thumbnail tile information is found.
        +
        +
      • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html index 592cc66411..087727cc88 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/package-summary.html @@ -262,6 +262,18 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +ConcatenatingMediaSource2 + +
      Concatenates multiple MediaSources, combining everything in one single Timeline.Window.
      + + + +ConcatenatingMediaSource2.Builder + +
      A builder for ConcatenatingMediaSource2 instances.
      + + + DefaultCompositeSequenceableLoaderFactory
      Default implementation of CompositeSequenceableLoaderFactory.
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html index cb4089cfe6..61ec428758 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/source/package-tree.html @@ -108,6 +108,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    • com.google.android.exoplayer2.source.CompositeMediaSource<T>
    • diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeAudioRenderer.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeAudioRenderer.html index 7c91fb3957..23129f6cc8 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeAudioRenderer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeAudioRenderer.html @@ -221,7 +221,7 @@ extends Description -FakeAudioRenderer​(Handler handler, +FakeAudioRenderer​(HandlerWrapper handler, AudioRendererEventListener eventListener)   @@ -318,13 +318,13 @@ extends + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaSource.InitialTimeline.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaSource.InitialTimeline.html index af516be795..d120d7663f 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaSource.InitialTimeline.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeMediaSource.InitialTimeline.html @@ -261,7 +261,7 @@ extends Timeline -equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundle +equals, getNextPeriodIndex, getPeriod, getPeriodByUid, getPeriodPosition, getPeriodPosition, getPeriodPositionUs, getPeriodPositionUs, getWindow, hashCode, isEmpty, isLastPeriod, toBundle, toBundleWithOneWindowOnly
      • @@ -795,7 +797,9 @@ extends public Looper getApplicationLooper()
        Description copied from interface: Player
        Returns the Looper associated with the application thread that's used to access the - player and on which player events are received.
        + player and on which player events are received. + +

        This method can be called from any thread.

      @@ -808,7 +812,9 @@ extends Description copied from interface: Player
      Registers a listener to receive all events from the player. -

      The listener's methods will be called on the thread associated with Player.getApplicationLooper().

      +

      The listener's methods will be called on the thread associated with Player.getApplicationLooper(). + +

      This method can be called from any thread.

      Parameters:
      listener - The listener to register.
      @@ -840,10 +846,10 @@ extends @State public @com.google.android.exoplayer2.Player.State int getPlaybackState()
      Description copied from interface: Player
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      Returns:
      -
      The current playback state.
      +
      The current playback state.
      See Also:
      Player.Listener.onPlaybackStateChanged(int)
      @@ -862,7 +868,7 @@ public @com.google.android.exoplayer2.Player.PlaybackSuppressionReason int& true
      , or Player.PLAYBACK_SUPPRESSION_REASON_NONE if playback is not suppressed.
      Returns:
      -
      The current playback suppression reason.
      +
      The current Player.PlaybackSuppressionReason.
      See Also:
      Player.Listener.onPlaybackSuppressionReasonChanged(int)
      @@ -901,6 +907,8 @@ public Description copied from interface: Player
      Prepares the player. +

      This method must only be called if Player.COMMAND_PREPARE is available. +

      This will move the player out of idle state and the player will start loading media and acquire resources needed for playback.

      @@ -914,10 +922,12 @@ public public void setMediaItems​(List<MediaItem> mediaItems, boolean resetPosition)
      Description copied from interface: Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      resetPosition - Whether the playback position should be reset to the default position in the first Timeline.Window. If false, playback will start from the position defined by Player.getCurrentMediaItemIndex() and Player.getCurrentPosition().
      @@ -934,10 +944,12 @@ public Description copied from interface: Player -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      mediaItems - The new MediaItems.
      +
      mediaItems - The new media items.
      startIndex - The MediaItem index to start playback from. If C.INDEX_UNSET is passed, the current position is not reset.
      startPositionMs - The position in milliseconds to start playback from. If C.TIME_UNSET is passed, the default position of the given MediaItem is used. In @@ -955,12 +967,14 @@ public public void addMediaItems​(int index, List<MediaItem> mediaItems)
      Description copied from interface: Player
      -
      Adds a list of media items at the given index of the playlist.
      +
      Adds a list of media items at the given index of the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      index - The index at which to add the media items. If the index is larger than the size of the playlist, the media items are added to the end of the playlist.
      -
      mediaItems - The MediaItems to add.
      +
      mediaItems - The media items to add.
      @@ -974,11 +988,15 @@ public Description copied from interface: Player -
      Moves the media item range to the new index.
      +
      Moves the media item range to the new index. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The start of the range to move.
      -
      toIndex - The first item not to be included in the range (exclusive).
      +
      fromIndex - The start of the range to move. If the index is larger than the size of the + playlist, the request is ignored.
      +
      toIndex - The first item not to be included in the range (exclusive). If the index is + larger than the size of the playlist, items up to the end of the playlist are moved.
      newIndex - The new index of the first media item of the range. If the new index is larger than the size of the remaining playlist after removing the range, the range is moved to the end of the playlist.
      @@ -994,12 +1012,15 @@ public public void removeMediaItems​(int fromIndex, int toIndex) -
      Removes a range of media items from the playlist.
      +
      Removes a range of media items from the playlist. + +

      This method must only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

      Parameters:
      -
      fromIndex - The index at which to start removing media items.
      +
      fromIndex - The index at which to start removing media items. If the index is larger than + the size of the playlist, the request is ignored.
      toIndex - The index of the first item to be kept (exclusive). If the index is larger than - the size of the playlist, media items to the end of the playlist are removed.
      + the size of the playlist, media items up to the end of the playlist are removed.
      @@ -1014,13 +1035,7 @@ public Returns the player's currently available Player.Commands.

      The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands - change. - -

      Executing a command that is not available (for example, calling Player.seekToNextMediaItem() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will - neither throw an exception nor generate a Player.getPlayerError() player error}. - -

      Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM and Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - are unavailable if there is no such MediaItem. + change.

      Returns:
      The currently available Player.Commands.
      @@ -1039,7 +1054,9 @@ public Description copied from interface: Player
      Sets whether playback should proceed when Player.getPlaybackState() == Player.STATE_READY. -

      If the player is already in the ready state then this method pauses and resumes playback.

      +

      If the player is already in the ready state then this method pauses and resumes playback. + +

      This method must only be called if Player.COMMAND_PLAY_PAUSE is available.

      Parameters:
      playWhenReady - Whether playback should proceed when ready.
      @@ -1072,7 +1089,9 @@ public public void setRepeatMode​(@RepeatMode @com.google.android.exoplayer2.Player.RepeatMode int repeatMode)
      Description copied from interface: Player
      -
      Sets the Player.RepeatMode to be used for playback.
      +
      Sets the Player.RepeatMode to be used for playback. + +

      This method must only be called if Player.COMMAND_SET_REPEAT_MODE is available.

      Parameters:
      repeatMode - The repeat mode.
      @@ -1104,7 +1123,9 @@ public public void setShuffleModeEnabled​(boolean shuffleModeEnabled) -
      Sets whether shuffling of media items is enabled.
      +
      Sets whether shuffling of media items is enabled. + +

      This method must only be called if Player.COMMAND_SET_SHUFFLE_MODE is available.

      Parameters:
      shuffleModeEnabled - Whether shuffling is enabled.
      @@ -1143,21 +1164,28 @@ public  +
      • seekTo

        public void seekTo​(int mediaItemIndex,
        -                   long positionMs)
        -
        Description copied from interface: Player
        -
        Seeks to a position specified in milliseconds in the specified MediaItem.
        + long positionMs, + @Command + @com.google.android.exoplayer2.Player.Command int seekCommand, + boolean isRepeatingCurrentItem) +
        Description copied from class: BasePlayer
        +
        Seeks to a position in the specified MediaItem.
        +
        Specified by:
        +
        seekTo in class BasePlayer
        Parameters:
        mediaItemIndex - The index of the MediaItem.
        -
        positionMs - The seek position in the specified MediaItem, or C.TIME_UNSET - to seek to the media item's default position.
        +
        positionMs - The seek position in the specified MediaItem in milliseconds, or + C.TIME_UNSET to seek to the media item's default position.
        +
        seekCommand - The Player.Command used to trigger the seek.
        +
        isRepeatingCurrentItem - Whether this seeks repeats the current item.
      @@ -1224,7 +1252,9 @@ public Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently - active playback parameters change. + active playback parameters change. + +

      This method must only be called if Player.COMMAND_SET_SPEED_AND_PITCH is available.

      Parameters:
      playbackParameters - The playback parameters.
      @@ -1263,7 +1293,9 @@ public Player.COMMAND_STOP is available. @@ -1301,7 +1333,9 @@ public void stop​(boolean reset)

      getCurrentTracks

      public Tracks getCurrentTracks()
      Description copied from interface: Player
      -
      Returns the current tracks.
      +
      Returns the current tracks. + +

      This method must only be called if Player.COMMAND_GET_TRACKS is available.

      See Also:
      Player.Listener.onTracksChanged(Tracks)
      @@ -1345,7 +1379,10 @@ public void stop​(boolean reset) .buildUpon() .setMaxVideoSizeSd() .build()) - +
      + +

      This method must only be called if Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS is + available. @@ -1362,7 +1399,9 @@ public void stop​(boolean reset)

      This MediaMetadata is a combination of the MediaItem metadata, the static metadata in the media's Format, and any timed metadata that has been parsed from the media and output via Player.Listener.onMetadata(Metadata). If a field is populated in the MediaItem.mediaMetadata, - it will be prioritised above the same field coming from static or timed metadata. + it will be prioritised above the same field coming from static or timed metadata. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available. @@ -1373,7 +1412,9 @@ public void stop​(boolean reset)

      getPlaylistMetadata

      public MediaMetadata getPlaylistMetadata()
      Description copied from interface: Player
      -
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported.
      +
      Returns the playlist MediaMetadata, as set by Player.setPlaylistMetadata(MediaMetadata), or MediaMetadata.EMPTY if not supported. + +

      This method must only be called if Player.COMMAND_GET_MEDIA_ITEMS_METADATA is available.

      @@ -1384,7 +1425,9 @@ public void stop​(boolean reset)

      setPlaylistMetadata

      public void setPlaylistMetadata​(MediaMetadata mediaMetadata)
      Description copied from interface: Player
      -
      Sets the playlist MediaMetadata.
      +
      Sets the playlist MediaMetadata. + +

      This method must only be called if Player.COMMAND_SET_MEDIA_ITEMS_METADATA is available.

      @@ -1395,7 +1438,9 @@ public void stop​(boolean reset)

      getCurrentTimeline

      public Timeline getCurrentTimeline()
      Description copied from interface: Player
      -
      Returns the current Timeline. Never null, but may be empty.
      +
      Returns the current Timeline. Never null, but may be empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      See Also:
      Player.Listener.onTimelineChanged(Timeline, int)
      @@ -1410,7 +1455,9 @@ public void stop​(boolean reset)

      getCurrentPeriodIndex

      public int getCurrentPeriodIndex()
      Description copied from interface: Player
      -
      Returns the index of the period currently being played.
      +
      Returns the index of the period currently being played. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available.

      @@ -1422,7 +1469,9 @@ public void stop​(boolean reset)
      public int getCurrentMediaItemIndex()
      Returns the index of the current MediaItem in the timeline, or the prospective index if the current timeline is - empty.
      + empty. + +

      This method must only be called if Player.COMMAND_GET_TIMELINE is available. @@ -1434,7 +1483,9 @@ public void stop​(boolean reset)

      public long getDuration()
      Returns the duration of the current content or ad in milliseconds, or C.TIME_UNSET if - the duration is not known.
      + the duration is not known. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1446,7 +1497,9 @@ public void stop​(boolean reset)

      public long getCurrentPosition()
      Returns the playback position in the current content or ad, in milliseconds, or the prospective - position in milliseconds if the current timeline is empty.
      + position in milliseconds if the current timeline is empty. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1458,7 +1511,9 @@ public void stop​(boolean reset)

      public long getBufferedPosition()
      Returns an estimate of the position in the current content or ad up to which data is buffered, - in milliseconds.
      + in milliseconds. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1470,7 +1525,9 @@ public void stop​(boolean reset)

      public long getTotalBufferedDuration()
      Returns an estimate of the total buffered duration from the current position, in milliseconds. - This includes pre-buffered data for subsequent ads and media items.
      + This includes pre-buffered data for subsequent ads and media items. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1481,7 +1538,9 @@ public void stop​(boolean reset)

      isPlayingAd

      public boolean isPlayingAd()
      -
      Returns whether the player is currently playing an ad.
      +
      Returns whether the player is currently playing an ad. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available.

      @@ -1493,7 +1552,9 @@ public void stop​(boolean reset)
      public int getCurrentAdGroupIndex()
      If Player.isPlayingAd() returns true, returns the index of the ad group in the period - currently being played. Returns C.INDEX_UNSET otherwise.
      + currently being played. Returns C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1505,7 +1566,9 @@ public void stop​(boolean reset)

      public int getCurrentAdIndexInAdGroup()
      If Player.isPlayingAd() returns true, returns the index of the ad in its ad group. Returns - C.INDEX_UNSET otherwise.
      + C.INDEX_UNSET otherwise. + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1518,7 +1581,9 @@ public void stop​(boolean reset)

      If Player.isPlayingAd() returns true, returns the content position that will be played once all ads in the ad group have finished playing, in milliseconds. If there is no ad - playing, the returned position is the same as that returned by Player.getCurrentPosition().
      + playing, the returned position is the same as that returned by Player.getCurrentPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1531,7 +1596,9 @@ public void stop​(boolean reset)

      If Player.isPlayingAd() returns true, returns an estimate of the content position in the current content up to which data is buffered, in milliseconds. If there is no ad playing, - the returned position is the same as that returned by Player.getBufferedPosition().
      + the returned position is the same as that returned by Player.getBufferedPosition(). + +

      This method must only be called if Player.COMMAND_GET_CURRENT_MEDIA_ITEM is available. @@ -1542,7 +1609,9 @@ public void stop​(boolean reset)

      getAudioAttributes

      public AudioAttributes getAudioAttributes()
      Description copied from interface: Player
      -
      Returns the attributes for audio playback.
      +
      Returns the attributes for audio playback. + +

      This method must only be called if Player.COMMAND_GET_AUDIO_ATTRIBUTES is available.

      @@ -1554,7 +1623,9 @@ public void stop​(boolean reset)
      public void setVolume​(float volume)
      Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal - unchanged), inclusive.
      + unchanged), inclusive. + +

      This method must only be called if Player.COMMAND_SET_VOLUME is available.

      Parameters:
      volume - Linear output gain to apply to all audio channels.
      @@ -1569,7 +1640,9 @@ public void stop​(boolean reset)

      getVolume

      public float getVolume()
      Description copied from interface: Player
      -
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      +
      Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged). + +

      This method must only be called if Player.COMMAND_GET_VOLUME is available.

      Returns:
      The linear gain applied to all audio channels.
      @@ -1585,7 +1658,9 @@ public void stop​(boolean reset)
      public void clearVideoSurface()
      Description copied from interface: Player
      Clears any Surface, SurfaceHolder, SurfaceView or TextureView - currently set on the player.
      + currently set on the player. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available. @@ -1598,7 +1673,9 @@ public void stop​(boolean reset) Surface surface)

      Description copied from interface: Player
      Clears the Surface onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surface - The surface to clear.
      @@ -1620,7 +1697,9 @@ public void stop​(boolean reset)

      If the surface is held by a SurfaceView, TextureView or SurfaceHolder then it's recommended to use Player.setVideoSurfaceView(SurfaceView), Player.setVideoTextureView(TextureView) or Player.setVideoSurfaceHolder(SurfaceHolder) rather than this method, since passing the holder allows the player to track the lifecycle of the surface - automatically. + automatically. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surface - The Surface.
      @@ -1640,7 +1719,9 @@ public void stop​(boolean reset) rendered. The player will track the lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceHolder - The surface holder.
      @@ -1657,7 +1738,9 @@ public void stop​(boolean reset) SurfaceHolder surfaceHolder)
      Description copied from interface: Player
      Clears the SurfaceHolder that holds the Surface onto which video is being - rendered if it matches the one passed. Else does nothing.
      + rendered if it matches the one passed. Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceHolder - The surface holder to clear.
      @@ -1677,7 +1760,9 @@ public void stop​(boolean reset) lifecycle of the surface automatically.

      The thread that calls the SurfaceHolder.Callback methods must be the thread - associated with Player.getApplicationLooper(). + associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceView - The surface view.
      @@ -1694,7 +1779,9 @@ public void stop​(boolean reset) SurfaceView surfaceView)
      Description copied from interface: Player
      Clears the SurfaceView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      surfaceView - The texture view to clear.
      @@ -1714,7 +1801,9 @@ public void stop​(boolean reset) lifecycle of the surface automatically.

      The thread that calls the TextureView.SurfaceTextureListener methods must be the - thread associated with Player.getApplicationLooper(). + thread associated with Player.getApplicationLooper(). + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view.
      @@ -1731,7 +1820,9 @@ public void stop​(boolean reset) TextureView textureView)
      Description copied from interface: Player
      Clears the TextureView onto which video is being rendered if it matches the one passed. - Else does nothing.
      + Else does nothing. + +

      This method must only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

      Parameters:
      textureView - The texture view to clear.
      @@ -1779,7 +1870,9 @@ public void stop​(boolean reset)

      getCurrentCues

      public CueGroup getCurrentCues()
      Description copied from interface: Player
      -
      Returns the current CueGroup.
      +
      Returns the current CueGroup. + +

      This method must only be called if Player.COMMAND_GET_TEXT is available.

      @@ -1809,7 +1902,9 @@ public void stop​(boolean reset) Util.getStreamTypeForAudioUsage(int).

      For devices with remote playback, the volume of the - remote device is returned. + remote device is returned. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available. @@ -1820,7 +1915,9 @@ public void stop​(boolean reset)

      isDeviceMuted

      public boolean isDeviceMuted()
      -
      Gets whether the device is muted or not.
      +
      Gets whether the device is muted or not. + +

      This method must only be called if Player.COMMAND_GET_DEVICE_VOLUME is available.

      @@ -1831,7 +1928,9 @@ public void stop​(boolean reset)

      setDeviceVolume

      public void setDeviceVolume​(int volume)
      -
      Sets the volume of the device.
      +
      Sets the volume of the device. + +

      This method must only be called if Player.COMMAND_SET_DEVICE_VOLUME is available.

      Parameters:
      volume - The volume to set.
      @@ -1846,7 +1945,9 @@ public void stop​(boolean reset)

      increaseDeviceVolume

      public void increaseDeviceVolume()
      Description copied from interface: Player
      -
      Increases the volume of the device.
      +
      Increases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      @@ -1857,7 +1958,9 @@ public void stop​(boolean reset)

      decreaseDeviceVolume

      public void decreaseDeviceVolume()
      -
      Decreases the volume of the device.
      +
      Decreases the volume of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      @@ -1868,7 +1971,9 @@ public void stop​(boolean reset)

      setDeviceMuted

      public void setDeviceMuted​(boolean muted)
      -
      Sets the mute state of the device.
      +
      Sets the mute state of the device. + +

      This method must only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME is available.

      diff --git a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html index 51beacedb2..f5919cb238 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html +++ b/docs/doc/reference/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.Parameters.html @@ -403,7 +403,7 @@ implements TrackSelectionParameters -fromBundle, keyForField +fromBundle diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/ListenerSet.html b/docs/doc/reference/com/google/android/exoplayer2/util/ListenerSet.html index 687a3faa3f..3a24a49f91 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/ListenerSet.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/ListenerSet.html @@ -25,8 +25,8 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10}; -var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":42,"i10":10}; +var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; var tableTab = "tableTab"; @@ -141,7 +141,10 @@ extends Events are also guaranteed to be only sent to the listeners registered at the time the event - was enqueued and haven't been removed since. + was enqueued and haven't been removed since. + +

      All methods must be called on the Looper passed to the constructor unless indicated + otherwise. @@ -214,7 +217,7 @@ extends

      Method Summary

      - + @@ -289,6 +292,15 @@ extends + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -419,70 +426,77 @@ extends Reads the next length bytes as characters in the specified Charset. - + - + - + - + - + - + - + - + - + - + + + + + + - + @@ -498,28 +512,28 @@ extends Updates the instance to wrap data, and resets the position to zero. - + - + - + - + + + + + +
      All Methods Instance Methods Concrete Methods All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and Type Method
      voidsetThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread) +
      Deprecated. +
      Do not use this method and ensure all calls are made from the correct thread.
      +
      +
      int size() @@ -332,7 +344,7 @@ extends
      Parameters:
      looper - A Looper used to call listeners on. The same Looper must be used - to call all other methods of this class.
      + to call all other methods of this class unless indicated otherwise.
      clock - A Clock.
      iterationFinishedEvent - An ListenerSet.IterationFinishedEvent sent when all other events sent during one Looper message queue iteration were handled by the listeners.
      @@ -358,7 +370,9 @@ extends @CheckResult public ListenerSet<T> copy​(Looper looper, ListenerSet.IterationFinishedEvent<T> iterationFinishedEvent) -
      Copies the listener set.
      +
      Copies the listener set. + +

      This method can be called from any thread.

      Parameters:
      looper - The new Looper for the copied listener set.
      @@ -379,7 +393,9 @@ public ListenerSet<T> copy​(Looper looper, Clock clock, ListenerSet.IterationFinishedEvent<T> iterationFinishedEvent) -
      Copies the listener set.
      +
      Copies the listener set. + +

      This method can be called from any thread.

      Parameters:
      looper - The new Looper for the copied listener set.
      @@ -402,7 +418,9 @@ public public void add​(T listener)
      Adds a listener to the set. -

      If a listener is already present, it will not be added again.

      +

      If a listener is already present, it will not be added again. + +

      This method can be called from any thread.

      Parameters:
      listener - The listener to be added.
      @@ -494,7 +512,7 @@ public  -
        +
        • release

          public void release()
          @@ -503,6 +521,26 @@ public  + + +
            +
          • +

            setThrowsWhenUsingWrongThread

            +
            @Deprecated
            +public void setThrowsWhenUsingWrongThread​(boolean throwsWhenUsingWrongThread)
            +
            Deprecated. +
            Do not use this method and ensure all calls are made from the correct thread.
            +
            +
            Sets whether methods throw when using the wrong thread. + +

            Do not use this method unless to support legacy use cases.

            +
            +
            Parameters:
            +
            throwsWhenUsingWrongThread - Whether to throw when using the wrong thread.
            +
            +
          • +
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/ParsableByteArray.html b/docs/doc/reference/com/google/android/exoplayer2/util/ParsableByteArray.html index 175bce0496..f7e9c391fe 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/ParsableByteArray.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/ParsableByteArray.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -316,52 +316,59 @@ extends String
      readLine() -
      Reads a line of text.
      +
      Reads a line of text in UTF-8.
      StringreadLine​(Charset charset) +
      Reads a line of text in charset.
      +
      int readLittleEndianInt()
      Reads the next four bytes as a signed value in little endian order.
      int readLittleEndianInt24()
      Reads the next three bytes as a signed value in little endian order.
      long readLittleEndianLong()
      Reads the next eight bytes as a signed value in little endian order.
      short readLittleEndianShort()
      Reads the next two bytes as a signed value.
      long readLittleEndianUnsignedInt()
      Reads the next four bytes as an unsigned value in little endian order.
      int readLittleEndianUnsignedInt24()
      Reads the next three bytes as an unsigned value in little endian order.
      int readLittleEndianUnsignedIntToInt() @@ -369,49 +376,49 @@ extends
      int readLittleEndianUnsignedShort()
      Reads the next two bytes as an unsigned value.
      long readLong()
      Reads the next eight bytes as a signed value.
      String readNullTerminatedString()
      Reads up to the next NUL byte (or the limit) as UTF-8 characters.
      String readNullTerminatedString​(int length)
      Reads the next length bytes as UTF-8 characters.
      short readShort()
      Reads the next two bytes as a signed value.
      String readString​(int length)
      Reads the next length bytes as UTF-8 characters.
      String readString​(int length, Charset charset)
      int readSynchSafeInt()
      Reads a Synchsafe integer.
      int readUnsignedByte()
      Reads the next byte as an unsigned value.
      int readUnsignedFixedPoint1616()
      Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer.
      long readUnsignedInt()
      Reads the next four bytes as an unsigned value.
      int readUnsignedInt24()
      Reads the next three bytes as an unsigned value.
      int readUnsignedIntToInt()
      Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero.
      long readUnsignedLongToLong()
      Reads the next eight bytes as an unsigned long into a long, if the top bit is a zero.
      int readUnsignedShort()
      Reads the next two bytes as an unsigned value.
      long readUtf8EncodedLong()
      Reads a long value encoded by UTF-8 encoding
      CharsetreadUtfCharsetFromBom() +
      Reads a UTF byte order mark (BOM) and returns the UTF Charset it represents.
      +
      void reset​(byte[] data) @@ -490,7 +504,7 @@ extends data.length.
      void reset​(byte[] data, int limit)
      void reset​(int limit)
      Resets the position to zero and the limit to the specified value.
      void setLimit​(int limit)
      Sets the limit.
      void setPosition​(int position)
      Sets the reading offset in the array.
      void skipBytes​(int bytes) @@ -1184,22 +1198,43 @@ public @Nullable public String readLine() -
      Reads a line of text. +
      Reads a line of text in UTF-8. + +

      Equivalent to passing Charsets.UTF_8 to readLine(Charset).

      + + + + + +
        +
      • +

        readLine

        +
        @Nullable
        +public String readLine​(Charset charset)
        +
        Reads a line of text in charset.

        A line is considered to be terminated by any one of a carriage return ('\r'), a line feed - ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The UTF-8 charset is - used. This method discards leading UTF-8 byte order marks, if present.

        + ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). This method discards + leading UTF byte order marks (BOM), if present. + +

        The position is advanced to start of the next line (i.e. any + line terminators are skipped).

      +
      Parameters:
      +
      charset - The charset used to interpret the bytes as a String.
      Returns:
      The line not including any line-termination characters, or null if the end of the data has already been reached.
      +
      Throws:
      +
      IllegalArgumentException - if charset is not supported. Only US_ASCII, UTF-8, UTF-16, + UTF-16BE, and UTF-16LE are supported.
      -
        +
        • readUtf8EncodedLong

          public long readUtf8EncodedLong()
          @@ -1212,6 +1247,18 @@ public  + + +
            +
          • +

            readUtfCharsetFromBom

            +
            @Nullable
            +public Charset readUtfCharsetFromBom()
            +
            Reads a UTF byte order mark (BOM) and returns the UTF Charset it represents. Returns + null without advancing position if no BOM is found.
            +
          • +
        diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/Size.html b/docs/doc/reference/com/google/android/exoplayer2/util/Size.html index ba739bbd55..e6033bd237 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/Size.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/Size.html @@ -159,6 +159,11 @@ extends A static instance to represent an unknown size value.
      static SizeZERO 
      @@ -257,13 +262,22 @@ extends -
        +
        • UNKNOWN

          public static final Size UNKNOWN
          A static instance to represent an unknown size value.
        + + + +
          +
        • +

          ZERO

          +
          public static final Size ZERO
          +
        • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/Util.html b/docs/doc/reference/com/google/android/exoplayer2/util/Util.html index 923701ff1d..9a88254b66 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/Util.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/Util.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9,"i25":9,"i26":9,"i27":9,"i28":9,"i29":9,"i30":9,"i31":9,"i32":9,"i33":9,"i34":9,"i35":9,"i36":9,"i37":9,"i38":9,"i39":9,"i40":9,"i41":9,"i42":9,"i43":9,"i44":9,"i45":9,"i46":9,"i47":9,"i48":9,"i49":9,"i50":9,"i51":9,"i52":9,"i53":9,"i54":9,"i55":9,"i56":9,"i57":9,"i58":9,"i59":9,"i60":9,"i61":9,"i62":9,"i63":9,"i64":9,"i65":9,"i66":9,"i67":9,"i68":9,"i69":9,"i70":9,"i71":41,"i72":41,"i73":9,"i74":9,"i75":9,"i76":9,"i77":9,"i78":9,"i79":9,"i80":9,"i81":9,"i82":9,"i83":9,"i84":9,"i85":9,"i86":9,"i87":9,"i88":9,"i89":9,"i90":9,"i91":9,"i92":9,"i93":9,"i94":9,"i95":9,"i96":9,"i97":9,"i98":9,"i99":9,"i100":9,"i101":9,"i102":9,"i103":9,"i104":9,"i105":9,"i106":9,"i107":9,"i108":9,"i109":9,"i110":9,"i111":9,"i112":9,"i113":9,"i114":9,"i115":9,"i116":9,"i117":9,"i118":9,"i119":9,"i120":9,"i121":9,"i122":9,"i123":9,"i124":9,"i125":9}; +var data = {"i0":9,"i1":9,"i2":9,"i3":9,"i4":9,"i5":9,"i6":9,"i7":9,"i8":9,"i9":9,"i10":9,"i11":9,"i12":9,"i13":9,"i14":9,"i15":9,"i16":9,"i17":9,"i18":9,"i19":9,"i20":9,"i21":9,"i22":9,"i23":9,"i24":9,"i25":9,"i26":9,"i27":9,"i28":9,"i29":9,"i30":9,"i31":9,"i32":9,"i33":9,"i34":9,"i35":9,"i36":9,"i37":9,"i38":9,"i39":9,"i40":9,"i41":9,"i42":9,"i43":9,"i44":9,"i45":9,"i46":9,"i47":9,"i48":9,"i49":9,"i50":9,"i51":9,"i52":9,"i53":9,"i54":9,"i55":9,"i56":9,"i57":9,"i58":9,"i59":9,"i60":9,"i61":9,"i62":9,"i63":9,"i64":9,"i65":9,"i66":9,"i67":9,"i68":9,"i69":9,"i70":9,"i71":9,"i72":41,"i73":41,"i74":9,"i75":9,"i76":9,"i77":9,"i78":9,"i79":9,"i80":9,"i81":9,"i82":9,"i83":9,"i84":9,"i85":9,"i86":9,"i87":9,"i88":9,"i89":9,"i90":9,"i91":9,"i92":9,"i93":9,"i94":9,"i95":9,"i96":9,"i97":9,"i98":9,"i99":9,"i100":9,"i101":9,"i102":9,"i103":9,"i104":9,"i105":9,"i106":9,"i107":9,"i108":9,"i109":9,"i110":9,"i111":9,"i112":9,"i113":9,"i114":9,"i115":9,"i116":9,"i117":9,"i118":9,"i119":9,"i120":9,"i121":9,"i122":9,"i123":9,"i124":9,"i125":9,"i126":9,"i127":9}; var tabs = {65535:["t0","All Methods"],1:["t1","Static Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -645,34 +645,43 @@ extends +static Drawable +getDrawable​(Context context, + Resources resources, + int drawableRes) + +
      Returns a Drawable for the given resource or throws a Resources.NotFoundException if not found.
      + + + static UUID getDrmUuid​(String drmScheme)
      Derives a DRM UUID from drmScheme.
      - + static @com.google.android.exoplayer2.PlaybackException.ErrorCode int getErrorCodeForMediaDrmErrorCode​(int mediaDrmErrorCode)
      Returns a PlaybackException.ErrorCode value that corresponds to the provided MediaDrm.ErrorCodes value.
      - + static int getErrorCodeFromPlatformDiagnosticsInfo​(String diagnosticsInfo)
      Attempts to parse an error code from a diagnostic string found in framework media exceptions.
      - + static String getFormatSupportString​(@com.google.android.exoplayer2.C.FormatSupport int formatSupport)
      Returns string representation of a C.FormatSupport flag.
      - + static int getIntegerCodeForString​(String string) @@ -680,14 +689,14 @@ extends - + static String getLocaleLanguageTag​(Locale locale)
      Returns the language tag for a Locale.
      - + static long getMediaDurationForPlayoutDuration​(long playoutDuration, float speed) @@ -695,21 +704,21 @@ extends Returns the duration of media that will elapse in playoutDuration. - + static long getNowUnixTimeMs​(long elapsedRealtimeEpochOffsetMs)
      Returns the current time in milliseconds since the epoch.
      - + static @com.google.android.exoplayer2.C.PcmEncoding int getPcmEncoding​(int bitDepth)
      Converts a sample bit depth to a corresponding PCM encoding constant.
      - + static Format getPcmFormat​(@com.google.android.exoplayer2.C.PcmEncoding int pcmEncoding, int channels, @@ -718,7 +727,7 @@ extends Gets a PCM Format with the specified parameters. - + static int getPcmFrameSize​(@com.google.android.exoplayer2.C.PcmEncoding int pcmEncoding, int channelCount) @@ -726,7 +735,7 @@ extends Returns the frame size for audio with channelCount channels in the specified encoding. - + static long getPlayoutDurationForMediaDuration​(long mediaDuration, float speed) @@ -734,14 +743,14 @@ extends Returns the playout duration of mediaDuration of media. - + static @com.google.android.exoplayer2.C.StreamType int getStreamTypeForAudioUsage​(@com.google.android.exoplayer2.C.AudioUsage int usage)
      Returns the C.StreamType corresponding to the specified C.AudioUsage.
      - + static String getStringForTime​(StringBuilder builder, Formatter formatter, @@ -750,7 +759,7 @@ extends Returns the specified millisecond time formatted as a string. - + static String[] getSystemLanguageCodes() @@ -758,14 +767,14 @@ extends - + static String getTrackTypeString​(@com.google.android.exoplayer2.C.TrackType int trackType)
      Returns a string representation of a C.TrackType.
      - + static String getUserAgent​(Context context, String applicationName) @@ -773,28 +782,28 @@ extends Returns a user agent string based on the given application name and the library version. - + static byte[] getUtf8Bytes​(String value)
      Returns a new byte array containing the code points of a String encoded using UTF-8.
      - + static byte[] gzip​(byte[] input)
      Compresses input using gzip and returns the result in a newly allocated byte array.
      - + static @com.google.android.exoplayer2.C.ContentType int inferContentType​(Uri uri)
      Makes a best guess to infer the C.ContentType from a Uri.
      - + static @com.google.android.exoplayer2.C.ContentType int inferContentType​(Uri uri, String overrideExtension) @@ -805,7 +814,7 @@ extends - + static @com.google.android.exoplayer2.C.ContentType int inferContentType​(String fileName) @@ -815,14 +824,14 @@ extends - + static @com.google.android.exoplayer2.C.ContentType int inferContentTypeForExtension​(String fileExtension)
      Makes a best guess to infer the C.ContentType from a file extension.
      - + static @com.google.android.exoplayer2.C.ContentType int inferContentTypeForUriAndMimeType​(Uri uri, String mimeType) @@ -830,7 +839,7 @@ extends Makes a best guess to infer the C.ContentType from a Uri and optional MIME type. - + static boolean inflate​(ParsableByteArray input, ParsableByteArray output, @@ -839,49 +848,56 @@ extends Uncompresses the data in input. - + +static String +intToStringMaxRadix​(int i) + +
      Returns a string representation of the integer using radix value Character.MAX_RADIX.
      + + + static boolean isAutomotive​(Context context)
      Returns whether the app is running on an automotive device.
      - + static boolean isEncodingHighResolutionPcm​(@com.google.android.exoplayer2.C.PcmEncoding int encoding)
      Returns whether encoding is high resolution (> 16-bit) PCM.
      - + static boolean isEncodingLinearPcm​(@com.google.android.exoplayer2.C.Encoding int encoding)
      Returns whether encoding is one of the linear PCM encodings.
      - + static boolean isLinebreak​(int c)
      Returns whether the given character is a carriage return ('\r') or a line feed ('\n').
      - + static boolean isLocalFileUri​(Uri uri)
      Returns true if the URI is a path to a local file or a reference to a local file.
      - + static boolean isTv​(Context context)
      Returns whether the app is running on a TV device.
      - + static int linearSearch​(int[] array, int value) @@ -889,7 +905,7 @@ extends Returns the index of the first occurrence of value in array, or C.INDEX_UNSET if value is not contained in array. - + static int linearSearch​(long[] array, long value) @@ -897,14 +913,14 @@ extends Returns the index of the first occurrence of value in array, or C.INDEX_UNSET if value is not contained in array. - + static long maxValue​(SparseLongArray sparseLongArray)
      Returns the maximum value in the given SparseLongArray.
      - + static boolean maybeRequestReadExternalStoragePermission​(Activity activity, Uri... uris) @@ -913,7 +929,7 @@ extends Uris, requesting the permission if necessary. - + static boolean maybeRequestReadExternalStoragePermission​(Activity activity, MediaItem... mediaItems) @@ -923,14 +939,14 @@ extends - + static long minValue​(SparseLongArray sparseLongArray)
      Returns the minimum value in the given SparseLongArray.
      - + static <T> void moveItems​(List<T> items, int fromIndex, @@ -940,28 +956,28 @@ extends Moves the elements starting at fromIndex to newFromIndex. - + static long msToUs​(long timeMs)
      Converts a time in milliseconds to the corresponding time in microseconds, preserving C.TIME_UNSET values and C.TIME_END_OF_SOURCE values.
      - + static ExecutorService newSingleThreadExecutor​(String threadName)
      Instantiates a new single threaded executor whose thread has the specified name.
      - + static @PolyNull String normalizeLanguageCode​(@PolyNull String language)
      Returns a normalized IETF BCP 47 language tag for language.
      - + static <T> T[] nullSafeArrayAppend​(T[] original, T newElement) @@ -969,7 +985,7 @@ extends Creates a new array containing original with newElement appended. - + static <T> T[] nullSafeArrayConcatenation​(T[] first, T[] second) @@ -977,7 +993,7 @@ extends Creates a new array containing the concatenation of two non-null type arrays. - + static <T> T[] nullSafeArrayCopy​(T[] input, int length) @@ -985,7 +1001,7 @@ extends Copies and optionally truncates an array. - + static <T> T[] nullSafeArrayCopyOfRange​(T[] input, int from, @@ -994,7 +1010,7 @@ extends Copies a subset of an array. - + static <T> void nullSafeListToArray​(List<T> list, T[] array) @@ -1002,7 +1018,7 @@ extends Copies the contents of list into array. - + static long parseXsDateTime​(String value) @@ -1010,14 +1026,14 @@ extends - + static long parseXsDuration​(String value)
      Parses an xs:duration attribute value, returning the parsed duration in milliseconds.
      - + static boolean postOrRun​(Handler handler, Runnable runnable) @@ -1025,7 +1041,7 @@ extends Posts the Runnable if the calling thread differs with the Looper of the Handler. - + static <T> ListenableFuture<T> postOrRunWithCompletion​(Handler handler, Runnable runnable, @@ -1034,7 +1050,7 @@ extends Posts the Runnable if the calling thread differs with the Looper of the Handler. - + static boolean readBoolean​(Parcel parcel) @@ -1042,14 +1058,14 @@ extends - + static void recursiveDelete​(File fileOrDirectory)
      Recursively deletes a directory and its content.
      - + static Intent registerReceiverNotExported​(Context context, BroadcastReceiver receiver, @@ -1059,7 +1075,7 @@ extends - + static Intent registerReceiverNotExported​(Context context, BroadcastReceiver receiver, @@ -1070,7 +1086,7 @@ extends - + static <T> void removeRange​(List<T> list, int fromIndex, @@ -1079,7 +1095,7 @@ extends Removes an indexed range from a List. - + static long scaleLargeTimestamp​(long timestamp, long multiplier, @@ -1088,7 +1104,7 @@ extends Scales a large timestamp. - + static long[] scaleLargeTimestamps​(List<Long> timestamps, long multiplier, @@ -1097,7 +1113,7 @@ extends Applies scaleLargeTimestamp(long, long, long) to a list of unscaled timestamps. - + static void scaleLargeTimestampsInPlace​(long[] timestamps, long multiplier, @@ -1106,7 +1122,7 @@ extends Applies scaleLargeTimestamp(long, long, long) to an array of unscaled timestamps. - + static void sneakyThrow​(Throwable t) @@ -1114,7 +1130,7 @@ extends - + static String[] split​(String value, String regex) @@ -1122,7 +1138,7 @@ extends Splits a string using value.split(regex, -1). - + static String[] splitAtFirst​(String value, String regex) @@ -1130,14 +1146,14 @@ extends Splits the string at the first occurrence of the delimiter regex. - + static String[] splitCodecs​(String codecs)
      Splits a codecs sequence string, as defined in RFC 6381, into individual codec strings.
      - + static ComponentName startForegroundService​(Context context, Intent intent) @@ -1146,7 +1162,7 @@ extends Context.startService(Intent) otherwise. - + static long subtractWithOverflowDefault​(long x, long y, @@ -1155,14 +1171,14 @@ extends Returns the difference between two arguments, or a third argument if the result overflows. - + static long sum​(long... summands)
      Returns the sum of all summands of the given array.
      - + static boolean tableExists​(SQLiteDatabase database, String tableName) @@ -1170,21 +1186,21 @@ extends Returns whether the table exists in the database. - + static byte[] toByteArray​(InputStream inputStream)
      Converts the entirety of an InputStream to a byte array.
      - + static String toHexString​(byte[] bytes)
      Returns a string containing a lower-case hex representation of the bytes provided.
      - + static long toLong​(int mostSignificantBits, int leastSignificantBits) @@ -1192,14 +1208,14 @@ extends Returns the long that is composed of the bits of the 2 specified integers. - + static long toUnsignedLong​(int x)
      Converts an integer to a long by unsigned conversion.
      - + static <T,​U>
      ListenableFuture<T>
      transformFutureAsync​(ListenableFuture<U> future, AsyncFunction<U,​T> transformFunction) @@ -1207,7 +1223,7 @@ extends Asynchronously transforms the result of a ListenableFuture. - + static CharSequence truncateAscii​(CharSequence sequence, int maxLength) @@ -1215,21 +1231,21 @@ extends Truncates a sequence of ASCII characters to a maximum length. - + static String unescapeFileName​(String fileName)
      Unescapes an escaped file or directory name back to its original value.
      - + static long usToMs​(long timeUs)
      Converts a time in microseconds to the corresponding time in milliseconds, preserving C.TIME_UNSET and C.TIME_END_OF_SOURCE values.
      - + static void writeBoolean​(Parcel parcel, boolean value) @@ -3670,7 +3686,7 @@ public static  -
      -A B C D E F G H I J K L M N O P Q R S T U V W X Y 
      All Classes All Packages + + + +

      Z

      +
      +
      ZERO - Static variable in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      +
      +
      An instance returning a constant position of zero.
      +
      +
      ZERO - Static variable in class com.google.android.exoplayer2.util.Size
      +
       
      +
      +A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
      All Classes All Packages
      -
      A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
      All Classes All Packages +
      A B C D E F G H I J K L M N O P Q R S T U V W X Y 
      All Classes All Packages

      A

      @@ -585,11 +585,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Factory for AdaptiveTrackSelection instances.
      -
      adBufferedPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently playing ad - is buffered, in milliseconds.
      -
      add(@com.google.android.exoplayer2.Player.Command int) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      @@ -614,24 +609,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Associates the specified value with the specified timestamp.
      -
      add(MediaItem) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Adds a MediaItem to the concatenation.
      -
      -
      add(MediaItem, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Adds a MediaItem to the concatenation and specifies its initial placeholder duration - used while the actual duration is still unknown.
      -
      -
      add(MediaSource) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Adds a MediaSource to the concatenation.
      -
      -
      add(MediaSource, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Adds a MediaSource to the concatenation and specifies its initial placeholder - duration used while the actual duration is still unknown.
      -
      add(Dumper.Dumpable) - Method in class com.google.android.exoplayer2.testutil.Dumper
       
      add(E) - Method in class com.google.android.exoplayer2.util.CopyOnWriteMultiset
      @@ -652,7 +629,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addAll(@com.google.android.exoplayer2.Player.Command int...) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Adds commands.
      +
      Adds commands.
      addAll(int...) - Method in class com.google.android.exoplayer2.util.FlagSet.Builder
      @@ -668,7 +645,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addAllCommands() - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Adds all existing commands.
      +
      Adds all existing commands.
      addAnalyticsListener(AnalyticsListener) - Method in interface com.google.android.exoplayer2.ExoPlayer
      @@ -1102,11 +1079,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The number of ad playbacks.
      -
      adPlaybackState - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
      -
      The AdPlaybackState of the period, or AdPlaybackState.NONE if there are no - ads.
      -
      AdPlaybackState - Class in com.google.android.exoplayer2.source.ads
      Represents ad group times and information on the state and URIs of ads within each ad group.
      @@ -1125,10 +1097,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      adPlaybackStates - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      -
      adPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The SimpleBasePlayer.PositionSupplier for the current ad playback position in milliseconds.
      -
      adResumePositionUs - Variable in class com.google.android.exoplayer2.source.ads.AdPlaybackState
      The position offset in the first unplayed ad at which to begin playback, in microseconds.
      @@ -2014,10 +1982,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      audioAttributes - Variable in class com.google.android.exoplayer2.audio.AudioAttributes.AudioAttributesV21
       
      -
      audioAttributes - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current AudioAttributes.
      -
      AudioAttributes - Class in com.google.android.exoplayer2.audio
      Attributes for audio playback, which configure the underlying platform AudioTrack.
      @@ -2487,10 +2451,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); bitmap should be displayed at its natural height given the bitmap dimensions and the specified Cue.size.
      -
      bitrate - Variable in class com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo
      -
      -
      The bitrate of audio samples.
      -
      bitrate - Variable in class com.google.android.exoplayer2.audio.MpegAudioUtil.Header
      Bitrate of the frame in bit/s.
      @@ -2769,14 +2729,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Builds a Player.Commands instance.
      -
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      - -
      -
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      - -
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      @@ -2787,10 +2739,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      build() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Builds the concatenating media source.
      -
      build() - Method in class com.google.android.exoplayer2.source.rtsp.RtpPacket.Builder
      Builds the RtpPacket.
      @@ -3010,11 +2958,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.ClippingConfiguration.Builder
      -
      Creates a new instance with default values.
      +
      Constructs an instance.
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.LiveConfiguration.Builder
      -
      Creates a new instance with default values.
      +
      Constructs an instance.
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.RequestMetadata.Builder
      @@ -3030,10 +2978,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates the builder.
      -
      Builder() - Constructor for class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Creates the builder.
      -
      Builder() - Constructor for class com.google.android.exoplayer2.source.rtsp.RtpPacket.Builder
       
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.DataSourceContractTest.TestResource.Builder
      @@ -3195,14 +3139,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a builder with the initial values specified in initialValues.
      -
      Builder(Object) - Constructor for class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Creates the builder.
      -
      -
      Builder(Object) - Constructor for class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      -
      Creates the builder.
      -
      Builder(String) - Constructor for class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
       
      Builder(String, Uri) - Constructor for class com.google.android.exoplayer2.offline.DownloadRequest.Builder
      @@ -3387,14 +3323,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a Player.Commands.Builder initialized with the values of this instance.
      -
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      Returns a SimpleBasePlayer.MediaItemData.Builder pre-populated with the current values.
      -
      -
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
      -
      Returns a SimpleBasePlayer.PeriodData.Builder pre-populated with the current values.
      -
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
      Returns a SimpleBasePlayer.State.Builder pre-populated with the current state values.
      @@ -4398,11 +4326,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      clearDecoderInfoCache() - Static method in class com.google.android.exoplayer2.mediacodec.MediaCodecUtil
       
      -
      clearDownloadManagerHelpers() - Static method in class com.google.android.exoplayer2.offline.DownloadService
      -
      -
      Clear all download manager helpers before restarting the - service.
      -
      clearFatalError() - Method in class com.google.android.exoplayer2.upstream.Loader
      Clears any stored fatal error.
      @@ -4474,10 +4397,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Removes all overrides of the provided track type.
      -
      clearPositionDiscontinuity() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Clears a previously set position discontinuity signal.
      -
      clearPrefixFlags(boolean[]) - Static method in class com.google.android.exoplayer2.util.NalUnitUtil
      @@ -5216,7 +5135,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_CHANGE_MEDIA_ITEMS - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to change the media items in the playlist.
      +
      Command to change the MediaItems in the playlist.
      COMMAND_GET_AUDIO_ATTRIBUTES - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5224,7 +5143,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_GET_CURRENT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to get information about the currently playing MediaItem.
      +
      Command to get the currently playing MediaItem.
      COMMAND_GET_DEVICE_VOLUME - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5232,7 +5151,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_GET_MEDIA_ITEMS_METADATA - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to get metadata related to the playlist and current MediaItem.
      +
      Command to get the MediaItems metadata.
      COMMAND_GET_TEXT - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5266,15 +5185,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_BACK - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek back by a fixed increment inside the current MediaItem.
      +
      Command to seek back by a fixed increment into the current MediaItem.
      COMMAND_SEEK_FORWARD - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek forward by a fixed increment inside the current MediaItem.
      +
      Command to seek forward by a fixed increment into the current MediaItem.
      COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek anywhere inside the current MediaItem.
      +
      Command to seek anywhere into the current MediaItem.
      COMMAND_SEEK_IN_CURRENT_WINDOW - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5292,8 +5211,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_TO_NEXT - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek to a later position in the current MediaItem or the default position of - the next MediaItem.
      +
      Command to seek to a later position in the current or next MediaItem.
      COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5307,8 +5225,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_TO_PREVIOUS - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek to an earlier position in the current MediaItem or the default position - of the previous MediaItem.
      +
      Command to seek to an earlier position in the current or previous MediaItem.
      COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5328,7 +5245,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SET_DEVICE_VOLUME - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to set the device volume.
      +
      Command to set the device volume and mute it.
      COMMAND_SET_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5336,7 +5253,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SET_MEDIA_ITEMS_METADATA - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to set the playlist metadata.
      +
      Command to set the MediaItems metadata.
      COMMAND_SET_REPEAT_MODE - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5364,7 +5281,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_STOP - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to stop playback.
      +
      Command to stop playback or release the player.
      commandBytes - Variable in class com.google.android.exoplayer2.metadata.scte35.PrivateCommand
      @@ -5465,14 +5382,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      ConcatenatingMediaSource(MediaSource...) - Constructor for class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      ConcatenatingMediaSource2 - Class in com.google.android.exoplayer2.source
      -
      -
      Concatenates multiple MediaSources, combining everything in one single Timeline.Window.
      -
      -
      ConcatenatingMediaSource2.Builder - Class in com.google.android.exoplayer2.source
      -
      -
      A builder for ConcatenatingMediaSource2 instances.
      -
      ConditionVariable - Class in com.google.android.exoplayer2.util
      An interruptible condition variable.
      @@ -5689,7 +5598,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      containsAny(@com.google.android.exoplayer2.Player.Event int...) - Method in class com.google.android.exoplayer2.Player.Events
      -
      Returns whether any of the given events occurred.
      +
      Returns whether any of the given events occurred.
      containsAny(int...) - Method in class com.google.android.exoplayer2.util.FlagSet
      @@ -5758,12 +5667,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      contentBufferedPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently playing - content is buffered, in milliseconds, or C.TIME_UNSET to indicate the default start - position.
      -
      ContentDataSource - Class in com.google.android.exoplayer2.upstream
      A DataSource for reading from a content URI.
      @@ -5816,11 +5719,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The content position, in milliseconds.
      -
      contentPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The SimpleBasePlayer.PositionSupplier for the current content playback position in milliseconds, or - C.TIME_UNSET to indicate the default start position.
      -
      contentResumeOffsetUs - Variable in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      The offset in microseconds which should be added to the content stream when resuming playback @@ -6810,8 +6708,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -7532,27 +7428,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returned by AudioSink.getCurrentPositionUs(boolean) when the position is not set.
      -
      currentAdGroupIndex - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current ad group index, or C.INDEX_UNSET if no ad is playing.
      -
      -
      currentAdIndexInAdGroup - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current ad index in the ad group, or C.INDEX_UNSET if no ad is playing.
      -
      currentCapacity - Variable in exception com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException
      The current capacity of the buffer.
      -
      currentCues - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current cues.
      -
      -
      currentMediaItemIndex - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current media item index, or C.INDEX_UNSET to assume the default first item of - the playlist is played.
      -
      currentMediaPeriodId - Variable in class com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime
      Media period identifier for the currently playing media period at the @@ -8090,12 +7969,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      DECODER_SUPPORT_FALLBACK - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      -
      The format exceeds the primary decoder's capabilities but is supported by fallback decoder
      +
      The renderer will use a fallback decoder.
      DECODER_SUPPORT_FALLBACK_MIMETYPE - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      -
      The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME - type.
      +
      The renderer will use a decoder for fallback mimetype if possible as format's MIME type is + unsupported
      DECODER_SUPPORT_PRIMARY - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -9364,11 +9243,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates session manager.
      -
      defaultPositionUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The default position relative to the start of the media item at which to begin playback, in - microseconds.
      -
      defaultPositionUs - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      defaultPositionUs - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -9674,10 +9548,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Requirement that the device's internal storage is not low.
      -
      deviceInfo - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      - -
      DeviceInfo - Class in com.google.android.exoplayer2
      Information about the playback device.
      @@ -9696,10 +9566,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      DeviceMappedEncoderBitrateProvider() - Constructor for class com.google.android.exoplayer2.transformer.DeviceMappedEncoderBitrateProvider
       
      -
      deviceVolume - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current device volume.
      -
      diagnosticInfo - Variable in exception com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
      An optional developer-readable diagnostic information string.
      @@ -9936,11 +9802,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Discontinuity introduced by a skipped period (for instance a skipped ad).
      -
      discontinuityPositionMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The position, in milliseconds, in the current content or ad from which playback continued - after the discontinuity.
      -
      discontinuitySequence - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      The discontinuity sequence number of the first media segment in the playlist, as defined by @@ -10507,14 +10368,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The duration of the track in microseconds, or C.TIME_UNSET if unknown.
      -
      durationUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The duration of the media item, in microseconds, or C.TIME_UNSET if unknown.
      -
      -
      durationUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
      -
      The total duration of the period, in microseconds, or C.TIME_UNSET if unknown.
      -
      durationUs - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      The total duration of the playlist in microseconds.
      @@ -10661,12 +10514,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      elapsedRealtime() - Method in class com.google.android.exoplayer2.util.SystemClock
       
      -
      elapsedRealtimeEpochOffsetMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The offset between SystemClock.elapsedRealtime() and the time since the Unix epoch - according to the clock of the media origin server, or C.TIME_UNSET if unknown or not - applicable.
      -
      elapsedRealtimeEpochOffsetMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The offset between SystemClock.elapsedRealtime() and the time since the Unix epoch @@ -10849,8 +10696,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      enableInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      enableInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      enableRenderer(int) - Method in class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
      Schedules a renderer enable action.
      @@ -10940,8 +10785,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      ENCODING_MP3 - Static variable in class com.google.android.exoplayer2.C
       
      -
      ENCODING_OPUS - Static variable in class com.google.android.exoplayer2.C
      -
       
      ENCODING_PCM_16BIT - Static variable in class com.google.android.exoplayer2.C
       
      ENCODING_PCM_16BIT_BIG_ENDIAN - Static variable in class com.google.android.exoplayer2.C
      @@ -11187,10 +11030,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      equals(Object) - Method in class com.google.android.exoplayer2.SeekParameters
       
      -
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
       
      -
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
       
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
       
      equals(Object) - Method in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      @@ -12811,7 +12650,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      FakeAudioRenderer(HandlerWrapper, AudioRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeAudioRenderer
      +
      FakeAudioRenderer(Handler, AudioRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeAudioRenderer
       
      FakeChunkSource - Class in com.google.android.exoplayer2.testutil
      @@ -13105,7 +12944,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      FakeVideoRenderer(HandlerWrapper, VideoRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeVideoRenderer
      +
      FakeVideoRenderer(Handler, VideoRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeVideoRenderer
       
      FALLBACK_TYPE_LOCATION - Static variable in interface com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy
      @@ -13749,10 +13588,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Moves UI focus to the skip button (or other interactive elements), if currently shown.
      -
      focusSkipButton() - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource.AdsLoader
      -
      -
      Puts the focus on the skip button, if a skip button is present and an ad is playing.
      -
      FOLDER_TYPE_ALBUMS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      Type for a folder containing media categorized by album.
      @@ -14390,10 +14225,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      GeobFrame(String, String, String, byte[]) - Constructor for class com.google.android.exoplayer2.metadata.id3.GeobFrame
       
      -
      get() - Method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      -
      -
      Returns the position.
      -
      get() - Method in class com.google.android.exoplayer2.util.RunnableFutureTask
       
      get(int) - Method in class com.google.android.exoplayer2.analytics.AnalyticsListener.Events
      @@ -14458,7 +14289,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      get(String, String) - Method in class com.google.android.exoplayer2.upstream.cache.DefaultContentMetadata
       
      -
      get1xBufferSizeInBytes(int, int, int, int, int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      get1xBufferSizeInBytes(int, int, int, int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      Returns the buffer size for playback at 1x speed.
      @@ -15046,12 +14877,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      getBufferingState() - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
       
      -
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, int, double) - Method in interface com.google.android.exoplayer2.audio.DefaultAudioSink.AudioTrackBufferSizeProvider
      +
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, double) - Method in interface com.google.android.exoplayer2.audio.DefaultAudioSink.AudioTrackBufferSizeProvider
      Returns the buffer size to use when creating an AudioTrack for a specific format and output mode.
      -
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, int, double) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, double) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
       
      getBuildConfig() - Static method in class com.google.android.exoplayer2.ext.vp9.VpxLibrary
      @@ -15353,10 +15184,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getConfigurationFormat() - Method in class com.google.android.exoplayer2.transformer.DefaultCodec
       
      -
      getConstant(long) - Static method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      -
      -
      Returns an instance that returns a constant value.
      -
      getContentBufferedPosition() - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      getContentBufferedPosition() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -15938,8 +15765,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getDecoderInfosSortedByFormatSupport(List<MediaCodecInfo>, Format) - Static method in class com.google.android.exoplayer2.mediacodec.MediaCodecUtil
      -
      Returns a copy of the provided decoder list sorted such that decoders with functional format - support are listed first.
      +
      Returns a copy of the provided decoder list sorted such that decoders with format support are + listed first.
      getDecoderSupport(@com.google.android.exoplayer2.RendererCapabilities.Capabilities int) - Static method in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -16107,10 +15934,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether downloads are currently paused.
      -
      getDrawable(Context, Resources, int) - Static method in class com.google.android.exoplayer2.util.Util
      -
      -
      Returns a Drawable for the given resource or throws a Resources.NotFoundException if not found.
      -
      getDrmUuid(String) - Static method in class com.google.android.exoplayer2.util.Util
      Derives a DRM UUID from drmScheme.
      @@ -16324,10 +16147,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns an ExtractorInput to read from the given input at given position.
      -
      getExtrapolating(long, float) - Static method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      -
      -
      Returns an instance that extrapolates the provided position into the future.
      -
      getFallbackSelectionFor(LoadErrorHandlingPolicy.FallbackOptions, LoadErrorHandlingPolicy.LoadErrorInfo) - Method in class com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy
      Returns whether a loader should fall back to using another resource on encountering an error, @@ -16635,8 +16454,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.LoopingMediaSource
      Deprecated.
      @@ -17164,8 +16981,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      getMediaItem() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -17210,7 +17025,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getMediaItemCount() - Method in interface com.google.android.exoplayer2.Player
      -
      Returns the number of media items in the playlist.
      +
      Returns the number of media items in the playlist.
      getMediaItemIndex() - Method in class com.google.android.exoplayer2.PlayerMessage
      @@ -17258,8 +17073,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getMediaPeriodIdForChildMediaPeriodId(MediaSource.MediaPeriodId, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.ads.AdsMediaSource
       
      -
      getMediaPeriodIdForChildMediaPeriodId(Integer, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      getMediaPeriodIdForChildMediaPeriodId(Integer, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.MergingMediaSource
       
      getMediaPeriodIdForChildMediaPeriodId(Void, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.WrappingMediaSource
      @@ -17713,17 +17526,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the selected track overrides.
      -
      getPacketDurationUs(byte[]) - Static method in class com.google.android.exoplayer2.audio.OpusUtil
      -
      -
      Returns the duration of the given audio packet.
      -
      getParameters() - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector
       
      getParameters() - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      Returns the current parameters for track selection.
      -
      getPassthroughBufferSizeInBytes(@com.google.android.exoplayer2.C.Encoding int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      getPassthroughBufferSizeInBytes(@com.google.android.exoplayer2.C.Encoding int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      Returns the buffer size for passthrough playback.
      @@ -17876,11 +17685,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the number of pixels if this is a video format whose Format.width and Format.height are known, or Format.NO_VALUE otherwise
      -
      getPlaceholderMediaItemData(MediaItem) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Returns the placeholder SimpleBasePlayer.MediaItemData used for a new MediaItem added to the - playlist.
      -
      getPlaceholderState(SimpleBasePlayer.State) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      Returns the placeholder state used while a player method is handled asynchronously.
      @@ -17945,7 +17749,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getPlaybackState() - Method in interface com.google.android.exoplayer2.Player
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      getPlaybackState() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -19915,8 +19719,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the window index in the composite source corresponding to the specified window index in a child source.
      -
      getWindowIndexForChildWindowIndex(Integer, int) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      getWindowIndexForChildWindowIndex(Void, int) - Method in class com.google.android.exoplayer2.source.WrappingMediaSource
       
      getWindowIndexForChildWindowIndex(T, int) - Method in class com.google.android.exoplayer2.source.CompositeMediaSource
      @@ -20097,10 +19899,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      H265SpsData(int, boolean, int, int, int[], int, int, int, int, float) - Constructor for class com.google.android.exoplayer2.util.NalUnitUtil.H265SpsData
       
      -
      handleAddMediaItems(int, List<MediaItem>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      handleBlockAddIDExtraData(MatroskaExtractor.Track, ExtractorInput, int) - Method in class com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor
       
      handleBlockAdditionalData(MatroskaExtractor.Track, int, ExtractorInput, int) - Method in class com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor
      @@ -20122,14 +19920,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      handleBuffer(ByteBuffer, long, int) - Method in class com.google.android.exoplayer2.testutil.CapturingAudioSink
       
      -
      handleClearVideoOutput(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to clear the video output.
      -
      -
      handleDecreaseDeviceVolume() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      handleDiscontinuity() - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Signals to the sink that the next buffer may be discontinuous with the previous buffer.
      @@ -20140,10 +19930,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      handleDiscontinuity() - Method in class com.google.android.exoplayer2.testutil.CapturingAudioSink
       
      -
      handleIncreaseDeviceVolume() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      handleInputBufferSupplementalData(DecoderInputBuffer) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecRenderer
      Handles supplemental data associated with an input buffer.
      @@ -20182,18 +19968,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Handles the message send to the component and additionally provides access to the player.
      -
      handleMoveMediaItems(int, int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      handlePendingSeek(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer2.extractor.BinarySearchSeeker
      Continues to handle the pending seek operation.
      -
      handlePrepare() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.prepare().
      -
      handlePrepareComplete(AdsMediaSource, int, int) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader
       
      handlePrepareComplete(AdsMediaSource, int, int) - Method in interface com.google.android.exoplayer2.source.ads.AdsLoader
      @@ -20206,14 +19984,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Notifies the ads loader that the player was not able to prepare media for a given ad.
      -
      handleRelease() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.release().
      -
      -
      handleRemoveMediaItems(int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      HandlerWrapper - Interface in com.google.android.exoplayer2.util
      An interface to call through to a Handler.
      @@ -20222,59 +19992,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A message obtained from the handler.
      -
      handleSeek(int, long, @com.google.android.exoplayer2.Player.Command int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.seekTo(long) and other seek operations (for example, Player.seekToNext()).
      -
      -
      handleSetDeviceMuted(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.setDeviceVolume(int).
      -
      -
      handleSetMediaItems(List<MediaItem>, int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      handleSetPlayWhenReady(boolean) - Method in class com.google.android.exoplayer2.LegacyMediaPlayerWrapper
       
      handleSetPlayWhenReady(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      - -
      -
      handleSetRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      - -
      -
      handleSetVideoOutput(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to set the video output.
      -
      -
      handleSetVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.setVolume(float).
      -
      -
      handleStop() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
      -
      Handles calls to Player.stop().
      +
      HARDWARE_ACCELERATION_NOT_SUPPORTED - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -20472,10 +20194,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      hashCode() - Method in class com.google.android.exoplayer2.SeekParameters
       
      -
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
       
      -
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
       
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
       
      hashCode() - Method in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      @@ -20702,10 +20420,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether all ads in the ad group at index adGroupIndex have been played, skipped or failed.
      -
      hasPositionDiscontinuity - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      Signals that a position discontinuity happened since the last update to the player.
      -
      hasPositiveStartOffset - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      Whether the HlsMediaPlaylist.startOffsetUs was explicitly defined by #EXT-X-START as a positive value @@ -21829,10 +21543,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      InternalFrame(String, String, String) - Constructor for class com.google.android.exoplayer2.metadata.id3.InternalFrame
       
      -
      intToStringMaxRadix(int) - Static method in class com.google.android.exoplayer2.util.Util
      -
      -
      Returns a string representation of the integer using radix value Character.MAX_RADIX.
      -
      invalidate() - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      Calls TrackSelector.InvalidationListener.onTrackSelectionsInvalidated() to invalidate all previously @@ -21988,10 +21698,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the track at the specified index in the selection is excluded.
      -
      isBrowsable - Variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      Optional boolean to indicate that the media is a browsable folder.
      -
      isCached - Variable in class com.google.android.exoplayer2.upstream.cache.CacheSpan
      Whether the CacheSpan is cached.
      @@ -22152,10 +21858,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the C.BUFFER_FLAG_DECODE_ONLY flag is set.
      -
      isDeviceMuted - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      Whether the device is muted.
      -
      isDeviceMuted() - Method in interface com.google.android.exoplayer2.ExoPlayer.DeviceComponent
      Deprecated. @@ -22184,10 +21886,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      isDone() - Method in class com.google.android.exoplayer2.util.RunnableFutureTask
       
      -
      isDynamic - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      Whether this media item may change over time, for example a moving live window.
      -
      isDynamic - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isDynamic - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -22341,14 +22039,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the given flag is set.
      -
      isFormatFunctionallySupported(Format) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecInfo
      -
      -
      Returns whether the decoder may functionally support decoding the given format.
      -
      isFormatSupported(Format) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecInfo
      -
      Returns whether the decoder may support decoding the given format both functionally and - performantly.
      +
      Returns whether the decoder may support decoding the given format.
      isFullyVisible() - Method in class com.google.android.exoplayer2.ui.StyledPlayerControlView
      @@ -22461,10 +22154,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      isLoadCompleted() - Method in class com.google.android.exoplayer2.testutil.FakeMediaChunk
       
      -
      isLoading - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      Whether the player is currently loading its source.
      -
      isLoading() - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      isLoading() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -22582,16 +22271,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the device can do passthrough playback for format.
      -
      isPlaceholder - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      Whether this media item contains placeholder information because the real information has yet - to be loaded.
      -
      -
      isPlaceholder - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
      -
      Whether this period contains placeholder information because the real information has yet to - be loaded.
      -
      isPlaceholder - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isPlaceholder - Variable in class com.google.android.exoplayer2.Timeline.Period
      @@ -22606,7 +22285,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      isPlayable - Variable in class com.google.android.exoplayer2.MediaMetadata
      -
      Optional boolean to indicate that the media is playable.
      +
      Optional boolean for media playability.
      isPlaying() - Method in class com.google.android.exoplayer2.BasePlayer
       
      @@ -22758,10 +22437,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the device supports secure placeholder surfaces.
      -
      isSeekable - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      Whether it's possible to seek within this media item.
      -
      isSeekable - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isSeekable - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -23131,6 +22806,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Key request type for keys that will be used for online use.
      +
      keyForField(int) - Static method in exception com.google.android.exoplayer2.PlaybackException
      +
      +
      Converts the given field number to a string which can be used as a field key when implementing + PlaybackException.toBundle() and Bundleable.Creator.
      +
      +
      keyForField(int) - Static method in class com.google.android.exoplayer2.trackselection.TrackSelectionParameters
      +
      +
      Converts the given field number to a string which can be used as a field key when implementing + TrackSelectionParameters.toBundle() and Bundleable.Creator.
      +
      KeyRequest(byte[], String) - Constructor for class com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
      @@ -23466,10 +23151,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The live playback configuration.
      -
      liveConfiguration - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The active MediaItem.LiveConfiguration, or null if the media item is not live.
      -
      liveConfiguration - Variable in class com.google.android.exoplayer2.Timeline.Window
      The MediaItem.LiveConfiguration that is used or null if Timeline.Window.isLive() returns @@ -23804,10 +23485,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The client manifest major version.
      -
      manifest - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The manifest of the media item, or null if not applicable.
      -
      manifest - Variable in class com.google.android.exoplayer2.Timeline.Window
      The manifest of the window.
      @@ -23927,10 +23604,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Holds data corresponding to a single track.
      -
      MAX_BYTES_PER_SECOND - Static variable in class com.google.android.exoplayer2.audio.OpusUtil
      -
      -
      Maximum achievable Opus bitrate.
      -
      MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY - Static variable in class com.google.android.exoplayer2.DefaultRenderersFactory
      The maximum number of frames that can be dropped between invocations of VideoRendererEventListener.onDroppedFrames(int, long).
      @@ -24050,11 +23723,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The maximum time spent during a single rebuffer, in milliseconds, or C.TIME_UNSET if no rebuffer occurred.
      -
      maxSeekToPreviousPositionMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, in - milliseconds.
      -
      maxValue(SparseLongArray) - Static method in class com.google.android.exoplayer2.util.Util
      Returns the maximum value in the given SparseLongArray.
      @@ -24287,156 +23955,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A seek to another media item has occurred.
      -
      MEDIA_TYPE_ALBUM - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items (e.g., music) belonging to an - album.
      -
      -
      MEDIA_TYPE_ARTIST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items (e.g., music) from the same - artist.
      -
      -
      MEDIA_TYPE_AUDIO_BOOK - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items forming an audio book.
      -
      -
      MEDIA_TYPE_AUDIO_BOOK_CHAPTER - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for an audio book chapter.
      -
      -
      MEDIA_TYPE_FOLDER_ALBUMS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing albums.
      -
      -
      MEDIA_TYPE_FOLDER_ARTISTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing artists.
      -
      -
      MEDIA_TYPE_FOLDER_AUDIO_BOOKS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing audio books.
      -
      -
      MEDIA_TYPE_FOLDER_GENRES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing genres.
      -
      -
      MEDIA_TYPE_FOLDER_MIXED - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder with mixed or undetermined content.
      -
      -
      MEDIA_TYPE_FOLDER_MOVIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing movies.
      -
      -
      MEDIA_TYPE_FOLDER_NEWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing news.
      -
      -
      MEDIA_TYPE_FOLDER_PLAYLISTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing playlists.
      -
      -
      MEDIA_TYPE_FOLDER_PODCASTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing podcasts.
      -
      -
      MEDIA_TYPE_FOLDER_RADIO_STATIONS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing radio - stations.
      -
      -
      MEDIA_TYPE_FOLDER_TRAILERS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing movie trailers.
      -
      -
      MEDIA_TYPE_FOLDER_TV_CHANNELS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing TV channels.
      -
      -
      MEDIA_TYPE_FOLDER_TV_SERIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing TV series.
      -
      -
      MEDIA_TYPE_FOLDER_TV_SHOWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing TV shows.
      -
      -
      MEDIA_TYPE_FOLDER_VIDEOS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing videos.
      -
      -
      MEDIA_TYPE_FOLDER_YEARS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a folder containing years.
      -
      -
      MEDIA_TYPE_GENRE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items (e.g., music) of the same - genre.
      -
      -
      MEDIA_TYPE_MIXED - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      Media of undetermined type or a mix of multiple media types.
      -
      -
      MEDIA_TYPE_MOVIE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      -
      MEDIA_TYPE_MUSIC - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      -
      MEDIA_TYPE_NEWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      -
      MEDIA_TYPE_PLAYLIST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items (e.g., music) forming a - playlist.
      -
      -
      MEDIA_TYPE_PODCAST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items belonging to a podcast.
      -
      -
      MEDIA_TYPE_PODCAST_EPISODE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a podcast episode.
      -
      -
      MEDIA_TYPE_RADIO_STATION - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a radio station.
      -
      -
      MEDIA_TYPE_TRAILER - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a movie trailer.
      -
      -
      MEDIA_TYPE_TV_CHANNEL - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items that are part of a TV channel.
      -
      -
      MEDIA_TYPE_TV_SEASON - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      -
      -
      MEDIA_TYPE_TV_SERIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      -
      -
      MEDIA_TYPE_TV_SHOW - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      -
      MEDIA_TYPE_VIDEO - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      -
      MEDIA_TYPE_YEAR - Static variable in class com.google.android.exoplayer2.MediaMetadata
      -
      -
      MediaMetadata.MediaType for a group of items (e.g., music) from the same - year.
      -
      MediaChunk - Class in com.google.android.exoplayer2.source.chunk
      An abstract base class for Chunks that contain media samples.
      @@ -24587,10 +24105,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media item, or null if the timeline is empty.
      -
      mediaItem - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      - -
      mediaItem - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      mediaItem - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -24707,11 +24221,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media metadata.
      -
      mediaMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The MediaMetadata, including static data from the MediaItem and the media's Format, as well any dynamic metadata that - has been parsed from the media.
      -
      MediaMetadata - Class in com.google.android.exoplayer2
      Metadata of a MediaItem, playlist, or a combination of multiple sources of Metadata.
      @@ -24724,10 +24233,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The folder type of the media item.
      -
      MediaMetadata.MediaType - Annotation Type in com.google.android.exoplayer2
      -
      -
      The type of content described by the media item.
      -
      MediaMetadata.PictureType - Annotation Type in com.google.android.exoplayer2
      The picture type of the artwork.
      @@ -24973,10 +24478,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media TrackGroup whose TrackSelectionOverride.trackIndices are forced to be selected.
      -
      mediaType - Variable in class com.google.android.exoplayer2.MediaMetadata
      -
      - -
      mediaUri - Variable in class com.google.android.exoplayer2.MediaItem.RequestMetadata
      The URI of the requested media, or null if not known or applicable.
      @@ -25794,11 +25295,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a newly created placeholder surface.
      -
      newlyRenderedFirstFrame - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      Whether a frame has been rendered for the first time since setting the surface, a rendering - reset, or since the stream being rendered was changed.
      -
      newMediaChunk(DefaultDashChunkSource.RepresentationHolder, DataSource, @com.google.android.exoplayer2.C.TrackType int, Format, @com.google.android.exoplayer2.C.SelectionReason int, Object, long, int, long, long) - Method in class com.google.android.exoplayer2.source.dash.DefaultDashChunkSource
       
      newNoDataInstance() - Static method in class com.google.android.exoplayer2.decoder.DecoderInputBuffer
      @@ -26222,7 +25718,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onAudioAttributesChanged(AudioAttributes) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getAudioAttributes() changes.
      +
      Called when the audio attributes change.
      onAudioCapabilitiesChanged(AudioCapabilities) - Method in interface com.google.android.exoplayer2.audio.AudioCapabilitiesReceiver.Listener
      @@ -26499,8 +25995,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called when the child source info has been refreshed.
      -
      onChildSourceInfoRefreshed(Integer, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      onChildSourceInfoRefreshed(Integer, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.source.MergingMediaSource
       
      onChildSourceInfoRefreshed(Void, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource
      @@ -26647,7 +26141,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onCues(CueGroup) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getCurrentCues() changes.
      +
      Called when there is a change in the CueGroup.
      onCues(CueGroup) - Method in interface com.google.android.exoplayer2.text.TextOutput
      @@ -26754,7 +26248,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onDeviceVolumeChanged(int, boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getDeviceVolume() or Player.isDeviceMuted() changes.
      +
      Called when the device volume or mute state changes.
      onDeviceVolumeChanged(AnalyticsListener.EventTime, int, boolean) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -27319,7 +26813,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onMediaMetadataChanged(MediaMetadata) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getMediaMetadata() changes.
      +
      Called when the combined MediaMetadata changes.
      onMessageArrived() - Method in interface com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget.Callback
      @@ -27416,7 +26910,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onPlaybackParametersChanged(PlaybackParameters) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getPlaybackParameters() changes.
      +
      Called when the current playback parameters change.
      onPlaybackSpeed(float) - Method in class com.google.android.exoplayer2.testutil.FakeTrackSelection
       
      @@ -27528,7 +27022,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onPlaylistMetadataChanged(MediaMetadata) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getPlaylistMetadata() changes.
      +
      Called when the playlist MediaMetadata changes.
      onPlayWhenReadyChanged(boolean) - Method in interface com.google.android.exoplayer2.trackselection.ExoTrackSelection
      @@ -28219,7 +27713,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onTimelineChanged(Timeline, @com.google.android.exoplayer2.Player.TimelineChangeReason int) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getCurrentTimeline() changes.
      +
      Called when the timeline has been refreshed.
      onTimelineChanged(Timeline, @com.google.android.exoplayer2.Player.TimelineChangeReason int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      @@ -28245,7 +27739,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onTracksChanged(Tracks) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getCurrentTracks() changes.
      +
      Called when the tracks change.
      onTrackSelectionChanged(boolean, Map<TrackGroup, TrackSelectionOverride>) - Method in interface com.google.android.exoplayer2.ui.TrackSelectionView.TrackSelectionListener
      @@ -28552,7 +28046,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onVolumeChanged(float) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the value of Player.getVolume() changes.
      +
      Called when the volume changes.
      onVolumeChanged(AnalyticsListener.EventTime, float) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -29136,10 +28630,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Parses the number of channels from the value attribute of an AudioChannelConfiguration with schemeIdUri "urn:mpeg:mpegB:cicp:ChannelConfiguration", as defined by ISO 23001-8 clause 8.1.
      -
      parsePacketAudioSampleCount(ByteBuffer) - Static method in class com.google.android.exoplayer2.audio.OpusUtil
      -
      -
      Returns the number of audio samples in the given audio packet.
      -
      parsePercentage(String) - Static method in class com.google.android.exoplayer2.text.webvtt.WebvttParserUtil
      Parses a percentage string.
      @@ -29212,10 +28702,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      parseText(XmlPullParser, String) - Static method in class com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
       
      -
      parseTileCountFromProperties(List<Descriptor>) - Method in class com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
      -
      -
      Parses given descriptors for thumbnail tile information.
      -
      parseTimestampUs(String) - Static method in class com.google.android.exoplayer2.text.webvtt.WebvttParserUtil
      Parses a WebVTT timestamp.
      @@ -29468,11 +28954,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The period index.
      -
      periods - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The list of periods in this media item, or an empty list to assume a - single period without ads and the same duration as the media item.
      -
      periodUid - Variable in class com.google.android.exoplayer2.Player.PositionInfo
      The UID of the period, or null if the timeline is empty.
      @@ -29783,10 +29264,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Class to capture output from a playback test.
      -
      playbackParameters - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The currently active PlaybackParameters.
      -
      PlaybackParameters - Class in com.google.android.exoplayer2
      Parameters that apply to playback, including speed setting.
      @@ -29821,10 +29298,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The playback state that became active.
      -
      playbackState - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The state of the player.
      -
      playbackStateHistory - Variable in class com.google.android.exoplayer2.analytics.PlaybackStats
      The playback state history as EventTimeAndPlaybackStates @@ -29859,10 +29332,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A listener for PlaybackStats updates.
      -
      playbackSuppressionReason - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The reason why playback is suppressed even if SimpleBasePlayer.getPlayWhenReady() is true.
      -
      playbackType - Variable in class com.google.android.exoplayer2.DeviceInfo
      The type of playback.
      @@ -29885,12 +29354,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Player.Command - Annotation Type in com.google.android.exoplayer2
      -
      Commands that indicate which method calls are currently permitted on a particular - Player instance.
      +
      Commands that can be executed on a Player.
      Player.Commands - Class in com.google.android.exoplayer2
      -
      A set of commands.
      +
      A set of commands.
      Player.Commands.Builder - Class in com.google.android.exoplayer2
      @@ -29906,11 +29374,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Player.Events - Class in com.google.android.exoplayer2
      -
      A set of events.
      +
      A set of events.
      Player.Listener - Interface in com.google.android.exoplayer2
      -
      Listener for changes in a Player.
      +
      Listener of all changes in the Player.
      Player.MediaItemTransitionReason - Annotation Type in com.google.android.exoplayer2
      @@ -29974,10 +29442,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Handles emsg messages for a specific track for the player.
      -
      playerError - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The last error that caused playback to fail, or null if there was no error.
      -
      PlayerId - Class in com.google.android.exoplayer2.analytics
      Identifier for a player instance.
      @@ -30067,20 +29531,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Determines when the buffering view is shown.
      -
      playlist - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The media items in the playlist.
      -
      PLAYLIST_TYPE_EVENT - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      PLAYLIST_TYPE_UNKNOWN - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      PLAYLIST_TYPE_VOD - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      -
      playlistMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The playlist MediaMetadata.
      -
      PlaylistResetException(Uri) - Constructor for exception com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker.PlaylistResetException
      Creates an instance.
      @@ -30190,14 +29646,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      populateMediaMetadata(MediaMetadata.Builder) - Method in class com.google.android.exoplayer2.metadata.id3.ApicFrame
       
      populateMediaMetadata(MediaMetadata.Builder) - Method in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
      -
      Uses the first element in TextInformationFrame.values to set the relevant field in MediaMetadata - (as determined by Id3Frame.id).
      -
      +
       
      populateMediaMetadata(MediaMetadata.Builder) - Method in interface com.google.android.exoplayer2.metadata.Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type-specific values stored in this - Entry.
      +
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      position - Variable in class com.google.android.exoplayer2.extractor.PositionHolder
      @@ -30258,21 +29710,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The cue box anchor positioned by Cue.position.
      -
      positionDiscontinuityReason - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The reason for the last position discontinuity.
      -
      PositionHolder - Class in com.google.android.exoplayer2.extractor
      Holds a position in the stream.
      PositionHolder() - Constructor for class com.google.android.exoplayer2.extractor.PositionHolder
       
      -
      positionInFirstPeriodUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The position of the start of this media item relative to the start of the first period - belonging to it, in microseconds.
      -
      positionInFirstPeriodUs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The position of the start of this window relative to the start of the first period belonging @@ -30508,8 +29951,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -30544,10 +29985,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Strategies controlling the layout of input pixels in the output frame.
      -
      presentationStartTimeMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The start time of the live presentation, in milliseconds since the Unix epoch, or C.TIME_UNSET if unknown or not applicable.
      -
      presentationStartTimeMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The start time of the presentation to which this window belongs in milliseconds since the @@ -31379,11 +30816,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      readLine() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      -
      Reads a line of text in UTF-8.
      -
      -
      readLine(Charset) - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      -
      -
      Reads a line of text in charset.
      +
      Reads a line of text.
      readLittleEndianInt() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      @@ -31511,10 +30944,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Reads a long value encoded by UTF-8 encoding
      -
      readUtfCharsetFromBom() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      -
      -
      Reads a UTF byte order mark (BOM) and returns the UTF Charset it represents.
      -
      readVorbisCommentHeader(ParsableByteArray) - Static method in class com.google.android.exoplayer2.extractor.VorbisUtil
      Reads a Vorbis comment header.
      @@ -32101,8 +31530,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -32165,8 +31592,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      -
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      -
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -32235,7 +31660,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeAll(@com.google.android.exoplayer2.Player.Command int...) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Removes commands.
      +
      Removes commands.
      removeAll(int...) - Method in class com.google.android.exoplayer2.util.FlagSet.Builder
      @@ -32685,9 +32110,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      "Repeat One" button enabled.
      -
      repeatMode - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      repeatCurrentMediaItem() - Method in class com.google.android.exoplayer2.BasePlayer
      -
      The Player.RepeatMode used for playback.
      +
      Repeat the current media item.
      RepeatModeActionProvider - Class in com.google.android.exoplayer2.ext.mediasession
      @@ -33564,11 +32989,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Runs tasks of the main Looper until a player error occurs.
      -
      runUntilIsLoading(Player, boolean) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      -
      -
      Runs tasks of the main Looper until Player.isLoading() matches the expected - value or a playback error occurs.
      -
      runUntilPendingCommandsAreFullyHandled(ExoPlayer) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      Runs tasks of the main Looper until the player completely handled all previously issued @@ -34067,10 +33487,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
      -
      seekBackIncrementMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The Player.seekBack() increment in milliseconds.
      -
      seekForward() - Method in class com.google.android.exoplayer2.BasePlayer
       
      seekForward() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -34082,10 +33498,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() milliseconds.
      -
      seekForwardIncrementMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The Player.seekForward() increment in milliseconds.
      -
      seekMap - Variable in class com.google.android.exoplayer2.extractor.BinarySearchSeeker
       
      seekMap - Variable in class com.google.android.exoplayer2.testutil.FakeExtractorOutput
      @@ -34146,7 +33558,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Attempts to seek the read position to the specified sample index.
      -
      seekTo(int, long) - Method in class com.google.android.exoplayer2.BasePlayer
      +
      seekTo(int, long) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      seekTo(int, long) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -34156,19 +33568,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks to a position specified in milliseconds in the specified MediaItem.
      -
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.BasePlayer
      -
      -
      Seeks to a position in the specified MediaItem.
      -
      -
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      +
      seekTo(int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
       
      -
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      +
      seekTo(int, long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
       
      -
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.testutil.StubPlayer
      +
      seekTo(int, long) - Method in class com.google.android.exoplayer2.testutil.StubPlayer
       
      seekTo(long) - Method in class com.google.android.exoplayer2.BasePlayer
       
      @@ -34832,11 +34238,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an ActionSchedule to be run by the test runner.
      -
      setAdBufferedPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently - playing ad is buffered, in milliseconds.
      -
      setAdErrorListener(AdErrorEvent.AdErrorListener) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader.Builder
      Sets a listener for ad errors that will be passed to AdsLoader.addAdErrorListener(AdErrorListener) and @@ -34868,22 +34269,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the MIME types to prioritize for linear ad media.
      -
      setAdPlaybackState(AdPlaybackState) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      -
      Sets the AdPlaybackState.
      -
      setAdPlaybackStates(ImmutableMap<Object, AdPlaybackState>) - Method in class com.google.android.exoplayer2.source.ads.ServerSideAdInsertionMediaSource
      Sets the map of ad playback states published by this source.
      -
      setAdPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current ad playback position in milliseconds.
      -
      -
      setAdPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the SimpleBasePlayer.PositionSupplier for the current ad playback position in milliseconds.
      -
      setAdPreloadTimeoutMs(long) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader.Builder
      Sets the duration in milliseconds for which the player must buffer while preloading an ad @@ -35168,10 +34557,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.audio.ForwardingAudioSink
       
      -
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current AudioAttributes.
      -
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector
       
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      @@ -35601,11 +34986,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the content of the output buffer, consisting of a Subtitle and associated metadata.
      -
      setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently - playing content is buffered, in milliseconds.
      -
      setContentLength(long) - Method in class com.google.android.exoplayer2.testutil.DownloadBuilder
       
      setContentLength(ContentMetadataMutations, long) - Static method in class com.google.android.exoplayer2.upstream.cache.ContentMetadataMutations
      @@ -35613,15 +34993,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Adds a mutation to set the ContentMetadata.KEY_CONTENT_LENGTH value, or to remove any existing value if C.LENGTH_UNSET is passed.
      -
      setContentPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current content playback position in milliseconds.
      -
      -
      setContentPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the SimpleBasePlayer.PositionSupplier for the current content playback position in - milliseconds.
      -
      setContentSourceId(String) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionUriBuilder
      The stream request content source ID used for on-demand streams.
      @@ -35745,18 +35116,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the cues to be displayed by the view.
      -
      setCurrentAd(int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current ad indices, or C.INDEX_UNSET if no ad is playing.
      -
      -
      setCurrentCues(CueGroup) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current cues.
      -
      -
      setCurrentMediaItemIndex(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current media item index.
      -
      setCurrentPosition(long) - Method in class com.google.android.exoplayer2.source.mediaparser.InputReaderAdapterV30
      Sets the absolute position in the resource from which the wrapped DataReader reads.
      @@ -35882,11 +35241,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the default artwork to display if useArtwork is true and no artwork is present in the media.
      -
      setDefaultPositionUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the default position relative to the start of the media item at which to begin - playback, in microseconds.
      -
      setDefaultRequestProperties(Map<String, String>) - Method in class com.google.android.exoplayer2.ext.cronet.CronetDataSource.Factory
       
      setDefaultRequestProperties(Map<String, String>) - Method in class com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource.Factory
      @@ -35929,10 +35283,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an optional, detailed reason that the view is on top of the player.
      -
      setDeviceInfo(DeviceInfo) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the DeviceInfo.
      -
      setDeviceMuted(boolean) - Method in interface com.google.android.exoplayer2.ExoPlayer.DeviceComponent
      Deprecated. @@ -35979,10 +35329,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current device volume.
      -
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -36164,23 +35510,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the duration of the video in milliseconds.
      -
      setDurationUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the duration of the media item, in microseconds.
      -
      -
      setDurationUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      -
      Sets the total duration of the period, in microseconds, or C.TIME_UNSET if unknown.
      -
      setDurationUs(long) - Method in class com.google.android.exoplayer2.source.SilenceMediaSource.Factory
      Sets the duration of the silent audio.
      -
      setElapsedRealtimeEpochOffsetMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the offset between SystemClock.elapsedRealtime() and the time since the Unix - epoch according to the clock of the media origin server.
      -
      setEnableAudioFloatOutput(boolean) - Method in class com.google.android.exoplayer2.DefaultRenderersFactory
      Sets whether floating point audio should be output when possible.
      @@ -36466,10 +35799,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets whether to focus the skip button (when available) on Android TV devices.
      -
      setFocusSkipButtonWhenAvailable(boolean) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.Builder
      -
      -
      Sets whether to focus the skip button (when available) on Android TV devices.
      -
      setFolderType(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      @@ -36739,46 +36068,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an int type uniform.
      -
      setIsBrowsable(Boolean) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      -
      -
      Sets whether the media is a browsable folder.
      -
      -
      setIsDeviceMuted(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets whether the device is muted.
      -
      setIsDisabled(boolean) - Method in class com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder
      Sets whether the selection is initially shown as disabled.
      -
      setIsDynamic(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets whether this media item may change over time, for example a moving live window.
      -
      -
      setIsLoading(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets whether the player is currently loading its source.
      -
      setIsNetwork(boolean) - Method in class com.google.android.exoplayer2.testutil.FakeDataSource.Factory
       
      -
      setIsPlaceholder(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets whether this media item contains placeholder information because the real information - has yet to be loaded.
      -
      -
      setIsPlaceholder(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      -
      Sets whether this period contains placeholder information because the real information has - yet to be loaded
      -
      setIsPlayable(Boolean) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets whether the media is playable.
      -
      setIsSeekable(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets whether it's possible to seek within this media item.
      -
      setItalic(boolean) - Method in class com.google.android.exoplayer2.text.webvtt.WebvttCssStyle
       
      setKeepContentOnPlayerReset(boolean) - Method in class com.google.android.exoplayer2.ui.PlayerView
      @@ -36944,10 +36243,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      setLiveConfiguration(MediaItem.LiveConfiguration) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the active MediaItem.LiveConfiguration, or null if the media item is not live.
      -
      setLiveMaxOffsetMs(long) - Method in class com.google.android.exoplayer2.MediaItem.Builder
      Deprecated. @@ -37108,10 +36403,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Looper that must be used for all calls to the transformer and that is used to call listeners on.
      -
      setManifest(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the manifest of the media item.
      -
      setManifest(Object) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder
      Sets a manifest to be used by a FakeMediaSource in the test runner.
      @@ -37194,11 +36485,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the maximum playback speed.
      -
      setMaxSeekToPreviousPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, - in milliseconds.
      -
      setMaxVideoBitrate(int) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters.Builder
       
      setMaxVideoBitrate(int) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder
      @@ -37267,14 +36553,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Clears the playlist, adds the specified MediaItem and resets the position to the default position.
      -
      setMediaItem(MediaItem) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the MediaItem.
      -
      -
      setMediaItem(MediaItem) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      -
      Sets the MediaItem to be used for the concatenated media source.
      -
      setMediaItem(MediaItem, boolean) - Method in class com.google.android.exoplayer2.BasePlayer
       
      setMediaItem(MediaItem, boolean) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -37307,8 +36585,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist, adds the specified media items and resets the - position to the default position.
      +
      Clears the playlist, adds the specified MediaItems and resets the position to + the default position.
      setMediaItems(List<MediaItem>, boolean) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      @@ -37318,7 +36596,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>, boolean) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist and adds the specified media items.
      +
      Clears the playlist and adds the specified MediaItems.
      setMediaItems(List<MediaItem>, boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -37336,7 +36614,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>, int, long) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist and adds the specified media items.
      +
      Clears the playlist and adds the specified MediaItems.
      setMediaItems(List<MediaItem>, int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -37358,10 +36636,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the media metadata.
      -
      setMediaMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the MediaMetadata.
      -
      setMediaMetadataProvider(MediaSessionConnector.MediaMetadataProvider) - Method in class com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
      Sets a provider of metadata to be published to the media session.
      @@ -37415,11 +36689,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      setMediaSourceFactory(MediaSource.Factory) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      - -
      setMediaSourceFactory(MediaSource.Factory) - Method in class com.google.android.exoplayer2.testutil.TestExoPlayerBuilder
      Sets the MediaSource.Factory to be used by the player.
      @@ -37475,10 +36744,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));  
      setMediaSources(List<MediaSource>, int, long) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      -
      setMediaType(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      -
      - -
      setMediaUri(Uri) - Method in class com.google.android.exoplayer2.MediaItem.RequestMetadata.Builder
      Sets the URI of the requested media, or null if not known or applicable.
      @@ -37605,11 +36870,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Overrides the network type.
      -
      setNewlyRenderedFirstFrame(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets whether a frame has been rendered for the first time since setting the surface, a - rendering reset, or since the stream being rendered was changed.
      -
      setNewSourceInfo(Timeline) - Method in class com.google.android.exoplayer2.testutil.FakeMediaSource
      Sets a new timeline.
      @@ -37857,10 +37117,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPercentDownloaded(float) - Method in class com.google.android.exoplayer2.testutil.DownloadBuilder
       
      -
      setPeriods(List<SimpleBasePlayer.PeriodData>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the list of periods in this media item.
      -
      setPitch(float) - Method in class com.google.android.exoplayer2.audio.SonicAudioProcessor
      Sets the target playback pitch.
      @@ -37877,10 +37133,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets whether to play an ad before the start position when beginning playback.
      -
      setPlaybackLooper(Looper) - Method in class com.google.android.exoplayer2.ExoPlayer.Builder
      -
      -
      Sets the Looper that will be used for playback.
      -
      setPlaybackParameters(PlaybackParameters) - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Attempts to set the playback parameters.
      @@ -37905,10 +37157,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the currently active PlaybackParameters.
      -
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -37953,14 +37201,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaybackSpeed(float, float) - Method in class com.google.android.exoplayer2.video.MediaCodecVideoRenderer
       
      -
      setPlaybackState(@com.google.android.exoplayer2.Player.State int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the state of the player.
      -
      -
      setPlaybackSuppressionReason(@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the reason why playback is suppressed even if SimpleBasePlayer.getPlayWhenReady() is true.
      -
      setPlayClearContentWithoutKey(boolean) - Method in class com.google.android.exoplayer2.MediaItem.DrmConfiguration.Builder
      Sets whether clear samples within protected content should be played when keys for the @@ -38026,10 +37266,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlayer(Player, Looper) - Method in class com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
       
      -
      setPlayerError(PlaybackException) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets last error that caused playback to fail, or null if there was no error.
      -
      setPlayerId(PlayerId) - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Sets the PlayerId of the player using this audio sink.
      @@ -38049,10 +37285,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an Player.Listener to be registered to listen to player events.
      setPlaylist(List<MediaItem>, MediaMetadata) - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
      -
      setPlaylist(List<SimpleBasePlayer.MediaItemData>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the list of media items in the playlist.
      -
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      This method is not supported and does nothing.
      @@ -38067,10 +37299,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the playlist MediaMetadata.
      -
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -38152,16 +37380,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the cue box anchor positioned by position.
      -
      setPositionDiscontinuity(@com.google.android.exoplayer2.Player.DiscontinuityReason int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Signals that a position discontinuity happened since the last player update and sets the - reason for it.
      -
      -
      setPositionInFirstPeriodUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the position of the start of this media item relative to the start of the first period - belonging to it, in microseconds.
      -
      setPositionUs(long) - Method in class com.google.android.exoplayer2.text.ExoplayerCuesDecoder
       
      setPositionUs(long) - Method in class com.google.android.exoplayer2.text.SimpleSubtitleDecoder
      @@ -38321,10 +37539,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets a listener for preparation events.
      -
      setPresentationStartTimeMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the start time of the live presentation.
      -
      setPreviousActionIconResourceId(int) - Method in class com.google.android.exoplayer2.ui.PlayerNotificationManager.Builder
      The resource id of the drawable to be used as the icon of action PlayerNotificationManager.ACTION_PREVIOUS.
      @@ -38537,12 +37751,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.RepeatMode to be used for playback.
      -
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
       
      -
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the Player.RepeatMode used for playback.
      -
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -38555,6 +37763,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      setRepeatMode(int) - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
       
      +
      setRepeatMode(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
       
      SetRepeatMode(String, @com.google.android.exoplayer2.Player.RepeatMode int) - Constructor for class com.google.android.exoplayer2.testutil.Action.SetRepeatMode
       
      setRepeatToggleModes(@com.google.android.exoplayer2.util.RepeatModeUtil.RepeatToggleModes int) - Method in class com.google.android.exoplayer2.ui.PlayerControlView
      @@ -38721,10 +37931,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.seekBack() increment.
      -
      setSeekBackIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the Player.seekBack() increment in milliseconds.
      -
      setSeekBackIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer.Builder
      Deprecated. @@ -38739,10 +37945,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.seekForward() increment.
      -
      setSeekForwardIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the Player.seekForward() increment in milliseconds.
      -
      setSeekForwardIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer.Builder
      Deprecated. @@ -39004,10 +38206,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets whether shuffling of media items is enabled.
      -
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -39238,10 +38436,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets a list of Formats to be used by a FakeMediaSource to create media periods.
      -
      setSurfaceSize(Size) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the size of the surface onto which the video is being rendered.
      -
      setTag(Object) - Method in class com.google.android.exoplayer2.MediaItem.Builder
      Sets the optional tag for custom attributes.
      @@ -39301,26 +38495,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the resource ID of the theme used to inflate this dialog.
      -
      setThrowsWhenUsingWrongThread(boolean) - Method in class com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
      -
      -
      Deprecated. -
      Do not use this method and ensure all calls are made from the correct thread.
      -
      -
      -
      setThrowsWhenUsingWrongThread(boolean) - Method in class com.google.android.exoplayer2.util.ListenerSet
      -
      -
      Deprecated. -
      Do not use this method and ensure all calls are made from the correct thread.
      -
      -
      -
      setTileCountHorizontal(int) - Method in class com.google.android.exoplayer2.Format.Builder
      -
      - -
      -
      setTileCountVertical(int) - Method in class com.google.android.exoplayer2.Format.Builder
      -
      - -
      setTimeBarMinUpdateInterval(int) - Method in class com.google.android.exoplayer2.ui.PlayerControlView
      Sets the minimum interval between time bar position updates.
      @@ -39329,10 +38503,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the minimum interval between time bar position updates.
      -
      setTimedMetadata(Metadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the most recent timed Metadata.
      -
      setTimeline(Timeline) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder
      Sets a Timeline to be used by a FakeMediaSource in the test runner.
      @@ -39357,11 +38527,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the input matrix to an identity matrix.
      -
      setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the SimpleBasePlayer.PositionSupplier for the estimated total buffered duration in - milliseconds.
      -
      setTotalDiscCount(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets the total number of discs.
      @@ -39397,10 +38562,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the track number.
      -
      setTracks(Tracks) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the Tracks of this media item.
      -
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -39413,10 +38574,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the currently active TrackSelectionParameters.
      -
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -39496,11 +38653,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the number of bytes searched to find a timestamp for TsExtractor instances created by the factory.
      -
      setTsSubtitleFormats(List<Format>) - Method in class com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
      -
      -
      Sets a list of subtitle formats to pass to the DefaultTsPayloadReaderFactory used by - TsExtractor instances created by the factory.
      -
      setTunnelingEnabled(boolean) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters.Builder
      Sets whether to enable tunneling if possible.
      @@ -39514,14 +38666,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the message type forwarded to PlayerMessage.Target.handleMessage(int, Object).
      -
      setUid(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the unique identifier of this media item within a playlist.
      -
      -
      setUid(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      -
      -
      Sets the unique identifier of the period within its media item.
      -
      setUnderline(boolean) - Method in class com.google.android.exoplayer2.text.webvtt.WebvttCssStyle
       
      setUnplayedColor(int) - Method in class com.google.android.exoplayer2.ui.DefaultTimeBar
      @@ -39830,10 +38974,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));  
      setVideoScalingMode(int) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      -
      setVideoSize(VideoSize) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current video size.
      -
      setVideoSurface() - Method in class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
      Schedules a set video surface action.
      @@ -40015,11 +39155,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      -
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      -
      -
      Sets the current audio volume, with 0 being silence and 1 being unity gain (signal - unchanged).
      -
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -40062,10 +39197,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the fill color of the window.
      -
      setWindowStartTimeMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      -
      -
      Sets the start time of the live window.
      -
      setWriter(CharSequence) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets the writer.
      @@ -40229,10 +39360,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Shows the scrubber handle with animation.
      -
      shuffleModeEnabled - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      Whether shuffling of media items is enabled.
      -
      ShuffleOrder - Interface in com.google.android.exoplayer2.source
      Shuffled order of indices.
      @@ -40301,28 +39428,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates the base class.
      -
      SimpleBasePlayer.MediaItemData - Class in com.google.android.exoplayer2
      -
      -
      An immutable description of an item in the playlist, containing both static setup information - like MediaItem and dynamic data that is generally read from the media like the - duration.
      -
      -
      SimpleBasePlayer.MediaItemData.Builder - Class in com.google.android.exoplayer2
      -
      -
      A builder for SimpleBasePlayer.MediaItemData objects.
      -
      -
      SimpleBasePlayer.PeriodData - Class in com.google.android.exoplayer2
      -
      -
      Data describing the properties of a period inside a SimpleBasePlayer.MediaItemData.
      -
      -
      SimpleBasePlayer.PeriodData.Builder - Class in com.google.android.exoplayer2
      -
      -
      A builder for SimpleBasePlayer.PeriodData objects.
      -
      -
      SimpleBasePlayer.PositionSupplier - Interface in com.google.android.exoplayer2
      -
      -
      A supplier for a position.
      -
      SimpleBasePlayer.State - Class in com.google.android.exoplayer2
      An immutable state description of the player.
      @@ -41937,10 +41042,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a new instance.
      -
      surfaceSize - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The size of the surface onto which the video is being rendered.
      -
      svcTemporalLayerCount - Variable in class com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry
      The number of layers in the SVC extended frames.
      @@ -42129,13 +41230,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Text information ID3 frame.
      TextInformationFrame(String, String, String) - Constructor for class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
      -
      Deprecated. -
      Use TextInformationFrame(String id, String description, String[] values - instead
      -
      -
      -
      TextInformationFrame(String, String, List<String>) - Constructor for class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
       
      TextOutput - Interface in com.google.android.exoplayer2.text
      @@ -42195,14 +41289,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a rated instance.
      -
      tileCountHorizontal - Variable in class com.google.android.exoplayer2.Format
      -
      -
      The number of horizontal tiles in an image, or Format.NO_VALUE if not known or applicable.
      -
      -
      tileCountVertical - Variable in class com.google.android.exoplayer2.Format
      -
      -
      The number of vertical tiles in an image, or Format.NO_VALUE if not known or applicable.
      -
      TIME_END_OF_SOURCE - Static variable in class com.google.android.exoplayer2.C
      Special constant representing a time corresponding to the end of a source.
      @@ -42220,10 +41306,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Listener for scrubbing events.
      -
      timedMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The most recent timed metadata.
      -
      TimedValueQueue<V> - Class in com.google.android.exoplayer2.util
      A utility class to keep a queue of values with timestamps.
      @@ -42242,10 +41324,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The Timeline in which the seek was attempted.
      -
      timeline - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      - -
      timeline - Variable in class com.google.android.exoplayer2.source.ForwardingTimeline
       
      Timeline - Class in com.google.android.exoplayer2
      @@ -42565,11 +41643,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a Bundle representing the information stored in this object.
      -
      toBundle(boolean, boolean) - Method in class com.google.android.exoplayer2.Player.PositionInfo
      -
      -
      Returns a Bundle representing the information stored in this object, filtered by - available commands.
      -
      +
      toBundle(boolean) - Method in class com.google.android.exoplayer2.Timeline
      toBundleArrayList(Collection<T>) - Static method in class com.google.android.exoplayer2.util.BundleableUtil
      Converts a collection of Bundleable to an ArrayList of Bundle so that @@ -42584,10 +41658,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      toBundleWithOneWindowOnly(int) - Method in class com.google.android.exoplayer2.Timeline
      -
      -
      Returns a Bundle containing just the specified Timeline.Window.
      -
      toByteArray(InputStream) - Static method in class com.google.android.exoplayer2.util.Util
      Converts the entirety of an InputStream to a byte array.
      @@ -42772,10 +41842,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Total buffered duration from AnalyticsListener.EventTime.currentPlaybackPositionMs at the time of the event, in milliseconds.
      -
      totalBufferedDurationMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The SimpleBasePlayer.PositionSupplier for the estimated total buffered duration in milliseconds.
      -
      totalDiscCount - Variable in class com.google.android.exoplayer2.MediaMetadata
      Optional total number of discs.
      @@ -43004,10 +42070,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      trackOutputs - Variable in class com.google.android.exoplayer2.testutil.FakeExtractorOutput
       
      -
      tracks - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The Tracks of this media item.
      -
      tracks - Variable in class com.google.android.exoplayer2.trackselection.BaseTrackSelection
      The indices of the selected tracks in BaseTrackSelection.group, in order of decreasing bandwidth.
      @@ -43087,10 +42149,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Constructs an instance to force trackIndices in trackGroup to be selected.
      -
      trackSelectionParameters - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The currently active TrackSelectionParameters.
      -
      TrackSelectionParameters - Class in com.google.android.exoplayer2.trackselection
      Parameters for controlling track selection.
      @@ -43620,14 +42678,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a UdpDataSourceException.
      -
      uid - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The unique identifier of this media item.
      -
      -
      uid - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      -
      -
      The unique identifier of the period within its media item.
      -
      uid - Variable in class com.google.android.exoplayer2.Timeline.Period
      A unique identifier for the period.
      @@ -44049,10 +43099,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      useBoundedDataSpecFor(String) - Method in class com.google.android.exoplayer2.testutil.CacheAsserts.RequestSet
       
      -
      useDefaultMediaSourceFactory(Context) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      -
      - -
      USER_DATA_IDENTIFIER_GA94 - Static variable in class com.google.android.exoplayer2.extractor.CeaUtil
       
      USER_DATA_TYPE_CODE_MPEG_CC - Static variable in class com.google.android.exoplayer2.extractor.CeaUtil
      @@ -44165,11 +43211,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The value.
      value - Variable in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
      -
      Deprecated. -
      Use the first element of TextInformationFrame.values instead.
      -
      -
      +
       
      value - Variable in class com.google.android.exoplayer2.metadata.mp4.MdtaMetadataEntry
      The payload.
      @@ -44184,10 +43226,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      value - Variable in class com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement
       
      -
      values - Variable in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
      -
      The text values of this frame.
      -
      variableDefinitions - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist
      Contains variable definitions, as defined by the #EXT-X-DEFINE tag.
      @@ -44451,10 +43489,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The size of the video data, in bytes.
      -
      videoSize - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current video size.
      -
      VideoSize - Class in com.google.android.exoplayer2.video
      Represents the video size.
      @@ -44499,10 +43533,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Viewport width in pixels.
      -
      volume - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      -
      The current audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      -
      VorbisBitArray - Class in com.google.android.exoplayer2.extractor
      Wraps a byte array, providing methods that allow it to be read as a Vorbis bitstream.
      @@ -44847,10 +43877,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The sequence number of the window in the buffered sequence of windows this media period is part of.
      -
      windowStartTimeMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      -
      -
      The start time of the live window, in milliseconds since the Unix epoch, or C.TIME_UNSET if unknown or not applicable.
      -
      windowStartTimeMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The window's start time in milliseconds since the Unix epoch, or C.TIME_UNSET if @@ -45232,19 +44258,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      yuvStrides - Variable in class com.google.android.exoplayer2.decoder.VideoDecoderOutputBuffer
       
      - - - -

      Z

      -
      -
      ZERO - Static variable in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      -
      -
      An instance returning a constant position of zero.
      -
      -
      ZERO - Static variable in class com.google.android.exoplayer2.util.Size
      -
       
      -
      -A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
      All Classes All Packages +A B C D E F G H I J K L M N O P Q R S T U V W X Y 
      All Classes All Packages
      -
      A B C D E F G H I J K L M N O P Q R S T U V W X Y 
      All Classes All Packages +
      A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
      All Classes All Packages

      A

      @@ -585,6 +585,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Factory for AdaptiveTrackSelection instances.
      +
      adBufferedPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently playing ad + is buffered, in milliseconds.
      +
      add(@com.google.android.exoplayer2.Player.Command int) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      @@ -609,6 +614,24 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Associates the specified value with the specified timestamp.
      +
      add(MediaItem) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Adds a MediaItem to the concatenation.
      +
      +
      add(MediaItem, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Adds a MediaItem to the concatenation and specifies its initial placeholder duration + used while the actual duration is still unknown.
      +
      +
      add(MediaSource) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Adds a MediaSource to the concatenation.
      +
      +
      add(MediaSource, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Adds a MediaSource to the concatenation and specifies its initial placeholder + duration used while the actual duration is still unknown.
      +
      add(Dumper.Dumpable) - Method in class com.google.android.exoplayer2.testutil.Dumper
       
      add(E) - Method in class com.google.android.exoplayer2.util.CopyOnWriteMultiset
      @@ -629,7 +652,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addAll(@com.google.android.exoplayer2.Player.Command int...) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Adds commands.
      +
      Adds commands.
      addAll(int...) - Method in class com.google.android.exoplayer2.util.FlagSet.Builder
      @@ -645,7 +668,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addAllCommands() - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Adds all existing commands.
      +
      Adds all existing commands.
      addAnalyticsListener(AnalyticsListener) - Method in interface com.google.android.exoplayer2.ExoPlayer
      @@ -1079,6 +1102,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The number of ad playbacks.
      +
      adPlaybackState - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
      +
      The AdPlaybackState of the period, or AdPlaybackState.NONE if there are no + ads.
      +
      AdPlaybackState - Class in com.google.android.exoplayer2.source.ads
      Represents ad group times and information on the state and URIs of ads within each ad group.
      @@ -1097,6 +1125,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      adPlaybackStates - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      +
      adPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The SimpleBasePlayer.PositionSupplier for the current ad playback position in milliseconds.
      +
      adResumePositionUs - Variable in class com.google.android.exoplayer2.source.ads.AdPlaybackState
      The position offset in the first unplayed ad at which to begin playback, in microseconds.
      @@ -1982,6 +2014,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      audioAttributes - Variable in class com.google.android.exoplayer2.audio.AudioAttributes.AudioAttributesV21
       
      +
      audioAttributes - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current AudioAttributes.
      +
      AudioAttributes - Class in com.google.android.exoplayer2.audio
      Attributes for audio playback, which configure the underlying platform AudioTrack.
      @@ -2451,6 +2487,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); bitmap should be displayed at its natural height given the bitmap dimensions and the specified Cue.size.
      +
      bitrate - Variable in class com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo
      +
      +
      The bitrate of audio samples.
      +
      bitrate - Variable in class com.google.android.exoplayer2.audio.MpegAudioUtil.Header
      Bitrate of the frame in bit/s.
      @@ -2729,6 +2769,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Builds a Player.Commands instance.
      +
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      + +
      +
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      + +
      build() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      @@ -2739,6 +2787,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      +
      build() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Builds the concatenating media source.
      +
      build() - Method in class com.google.android.exoplayer2.source.rtsp.RtpPacket.Builder
      Builds the RtpPacket.
      @@ -2958,11 +3010,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.ClippingConfiguration.Builder
      -
      Constructs an instance.
      +
      Creates a new instance with default values.
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.LiveConfiguration.Builder
      -
      Constructs an instance.
      +
      Creates a new instance with default values.
      Builder() - Constructor for class com.google.android.exoplayer2.MediaItem.RequestMetadata.Builder
      @@ -2978,6 +3030,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates the builder.
      +
      Builder() - Constructor for class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Creates the builder.
      +
      Builder() - Constructor for class com.google.android.exoplayer2.source.rtsp.RtpPacket.Builder
       
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.DataSourceContractTest.TestResource.Builder
      @@ -3139,6 +3195,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a builder with the initial values specified in initialValues.
      +
      Builder(Object) - Constructor for class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Creates the builder.
      +
      +
      Builder(Object) - Constructor for class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      +
      Creates the builder.
      +
      Builder(String) - Constructor for class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
       
      Builder(String, Uri) - Constructor for class com.google.android.exoplayer2.offline.DownloadRequest.Builder
      @@ -3323,6 +3387,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a Player.Commands.Builder initialized with the values of this instance.
      +
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      Returns a SimpleBasePlayer.MediaItemData.Builder pre-populated with the current values.
      +
      +
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
      +
      Returns a SimpleBasePlayer.PeriodData.Builder pre-populated with the current values.
      +
      buildUpon() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
      Returns a SimpleBasePlayer.State.Builder pre-populated with the current state values.
      @@ -4326,6 +4398,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      clearDecoderInfoCache() - Static method in class com.google.android.exoplayer2.mediacodec.MediaCodecUtil
       
      +
      clearDownloadManagerHelpers() - Static method in class com.google.android.exoplayer2.offline.DownloadService
      +
      +
      Clear all download manager helpers before restarting the + service.
      +
      clearFatalError() - Method in class com.google.android.exoplayer2.upstream.Loader
      Clears any stored fatal error.
      @@ -4397,6 +4474,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Removes all overrides of the provided track type.
      +
      clearPositionDiscontinuity() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Clears a previously set position discontinuity signal.
      +
      clearPrefixFlags(boolean[]) - Static method in class com.google.android.exoplayer2.util.NalUnitUtil
      @@ -5135,7 +5216,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_CHANGE_MEDIA_ITEMS - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to change the MediaItems in the playlist.
      +
      Command to change the media items in the playlist.
      COMMAND_GET_AUDIO_ATTRIBUTES - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5143,7 +5224,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_GET_CURRENT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to get the currently playing MediaItem.
      +
      Command to get information about the currently playing MediaItem.
      COMMAND_GET_DEVICE_VOLUME - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5151,7 +5232,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_GET_MEDIA_ITEMS_METADATA - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to get the MediaItems metadata.
      +
      Command to get metadata related to the playlist and current MediaItem.
      COMMAND_GET_TEXT - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5185,15 +5266,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_BACK - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek back by a fixed increment into the current MediaItem.
      +
      Command to seek back by a fixed increment inside the current MediaItem.
      COMMAND_SEEK_FORWARD - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek forward by a fixed increment into the current MediaItem.
      +
      Command to seek forward by a fixed increment inside the current MediaItem.
      COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek anywhere into the current MediaItem.
      +
      Command to seek anywhere inside the current MediaItem.
      COMMAND_SEEK_IN_CURRENT_WINDOW - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5211,7 +5292,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_TO_NEXT - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek to a later position in the current or next MediaItem.
      +
      Command to seek to a later position in the current MediaItem or the default position of + the next MediaItem.
      COMMAND_SEEK_TO_NEXT_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5225,7 +5307,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SEEK_TO_PREVIOUS - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to seek to an earlier position in the current or previous MediaItem.
      +
      Command to seek to an earlier position in the current MediaItem or the default position + of the previous MediaItem.
      COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5245,7 +5328,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SET_DEVICE_VOLUME - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to set the device volume and mute it.
      +
      Command to set the device volume.
      COMMAND_SET_MEDIA_ITEM - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5253,7 +5336,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_SET_MEDIA_ITEMS_METADATA - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to set the MediaItems metadata.
      +
      Command to set the playlist metadata.
      COMMAND_SET_REPEAT_MODE - Static variable in interface com.google.android.exoplayer2.Player
      @@ -5281,7 +5364,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      COMMAND_STOP - Static variable in interface com.google.android.exoplayer2.Player
      -
      Command to stop playback or release the player.
      +
      Command to stop playback.
      commandBytes - Variable in class com.google.android.exoplayer2.metadata.scte35.PrivateCommand
      @@ -5382,6 +5465,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      ConcatenatingMediaSource(MediaSource...) - Constructor for class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      ConcatenatingMediaSource2 - Class in com.google.android.exoplayer2.source
      +
      +
      Concatenates multiple MediaSources, combining everything in one single Timeline.Window.
      +
      +
      ConcatenatingMediaSource2.Builder - Class in com.google.android.exoplayer2.source
      +
      +
      A builder for ConcatenatingMediaSource2 instances.
      +
      ConditionVariable - Class in com.google.android.exoplayer2.util
      An interruptible condition variable.
      @@ -5598,7 +5689,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      containsAny(@com.google.android.exoplayer2.Player.Event int...) - Method in class com.google.android.exoplayer2.Player.Events
      -
      Returns whether any of the given events occurred.
      +
      Returns whether any of the given events occurred.
      containsAny(int...) - Method in class com.google.android.exoplayer2.util.FlagSet
      @@ -5667,6 +5758,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      +
      contentBufferedPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently playing + content is buffered, in milliseconds, or C.TIME_UNSET to indicate the default start + position.
      +
      ContentDataSource - Class in com.google.android.exoplayer2.upstream
      A DataSource for reading from a content URI.
      @@ -5719,6 +5816,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The content position, in milliseconds.
      +
      contentPositionMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The SimpleBasePlayer.PositionSupplier for the current content playback position in milliseconds, or + C.TIME_UNSET to indicate the default start position.
      +
      contentResumeOffsetUs - Variable in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      The offset in microseconds which should be added to the content stream when resuming playback @@ -6708,6 +6810,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      createPeriod(MediaSource.MediaPeriodId, Allocator, long) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -7428,10 +7532,27 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returned by AudioSink.getCurrentPositionUs(boolean) when the position is not set.
      +
      currentAdGroupIndex - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current ad group index, or C.INDEX_UNSET if no ad is playing.
      +
      +
      currentAdIndexInAdGroup - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current ad index in the ad group, or C.INDEX_UNSET if no ad is playing.
      +
      currentCapacity - Variable in exception com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException
      The current capacity of the buffer.
      +
      currentCues - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current cues.
      +
      +
      currentMediaItemIndex - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current media item index, or C.INDEX_UNSET to assume the default first item of + the playlist is played.
      +
      currentMediaPeriodId - Variable in class com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime
      Media period identifier for the currently playing media period at the @@ -7969,12 +8090,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      DECODER_SUPPORT_FALLBACK - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      -
      The renderer will use a fallback decoder.
      +
      The format exceeds the primary decoder's capabilities but is supported by fallback decoder
      DECODER_SUPPORT_FALLBACK_MIMETYPE - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      -
      The renderer will use a decoder for fallback mimetype if possible as format's MIME type is - unsupported
      +
      The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME + type.
      DECODER_SUPPORT_PRIMARY - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -9243,6 +9364,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates session manager.
      +
      defaultPositionUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The default position relative to the start of the media item at which to begin playback, in + microseconds.
      +
      defaultPositionUs - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      defaultPositionUs - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -9548,6 +9674,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Requirement that the device's internal storage is not low.
      +
      deviceInfo - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      + +
      DeviceInfo - Class in com.google.android.exoplayer2
      Information about the playback device.
      @@ -9566,6 +9696,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      DeviceMappedEncoderBitrateProvider() - Constructor for class com.google.android.exoplayer2.transformer.DeviceMappedEncoderBitrateProvider
       
      +
      deviceVolume - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current device volume.
      +
      diagnosticInfo - Variable in exception com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
      An optional developer-readable diagnostic information string.
      @@ -9802,6 +9936,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Discontinuity introduced by a skipped period (for instance a skipped ad).
      +
      discontinuityPositionMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The position, in milliseconds, in the current content or ad from which playback continued + after the discontinuity.
      +
      discontinuitySequence - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      The discontinuity sequence number of the first media segment in the playlist, as defined by @@ -10368,6 +10507,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The duration of the track in microseconds, or C.TIME_UNSET if unknown.
      +
      durationUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The duration of the media item, in microseconds, or C.TIME_UNSET if unknown.
      +
      +
      durationUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
      +
      The total duration of the period, in microseconds, or C.TIME_UNSET if unknown.
      +
      durationUs - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      The total duration of the playlist in microseconds.
      @@ -10514,6 +10661,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      elapsedRealtime() - Method in class com.google.android.exoplayer2.util.SystemClock
       
      +
      elapsedRealtimeEpochOffsetMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The offset between SystemClock.elapsedRealtime() and the time since the Unix epoch + according to the clock of the media origin server, or C.TIME_UNSET if unknown or not + applicable.
      +
      elapsedRealtimeEpochOffsetMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The offset between SystemClock.elapsedRealtime() and the time since the Unix epoch @@ -10696,6 +10849,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      enableInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      enableInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      enableRenderer(int) - Method in class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
      Schedules a renderer enable action.
      @@ -10785,6 +10940,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      ENCODING_MP3 - Static variable in class com.google.android.exoplayer2.C
       
      +
      ENCODING_OPUS - Static variable in class com.google.android.exoplayer2.C
      +
       
      ENCODING_PCM_16BIT - Static variable in class com.google.android.exoplayer2.C
       
      ENCODING_PCM_16BIT_BIG_ENDIAN - Static variable in class com.google.android.exoplayer2.C
      @@ -11030,6 +11187,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      equals(Object) - Method in class com.google.android.exoplayer2.SeekParameters
       
      +
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
       
      +
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
       
      equals(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
       
      equals(Object) - Method in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      @@ -12650,7 +12811,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      FakeAudioRenderer(Handler, AudioRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeAudioRenderer
      +
      FakeAudioRenderer(HandlerWrapper, AudioRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeAudioRenderer
       
      FakeChunkSource - Class in com.google.android.exoplayer2.testutil
      @@ -12944,7 +13105,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      -
      FakeVideoRenderer(Handler, VideoRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeVideoRenderer
      +
      FakeVideoRenderer(HandlerWrapper, VideoRendererEventListener) - Constructor for class com.google.android.exoplayer2.testutil.FakeVideoRenderer
       
      FALLBACK_TYPE_LOCATION - Static variable in interface com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy
      @@ -13588,6 +13749,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Moves UI focus to the skip button (or other interactive elements), if currently shown.
      +
      focusSkipButton() - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource.AdsLoader
      +
      +
      Puts the focus on the skip button, if a skip button is present and an ad is playing.
      +
      FOLDER_TYPE_ALBUMS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      Type for a folder containing media categorized by album.
      @@ -14225,6 +14390,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      GeobFrame(String, String, String, byte[]) - Constructor for class com.google.android.exoplayer2.metadata.id3.GeobFrame
       
      +
      get() - Method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      +
      +
      Returns the position.
      +
      get() - Method in class com.google.android.exoplayer2.util.RunnableFutureTask
       
      get(int) - Method in class com.google.android.exoplayer2.analytics.AnalyticsListener.Events
      @@ -14289,7 +14458,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      get(String, String) - Method in class com.google.android.exoplayer2.upstream.cache.DefaultContentMetadata
       
      -
      get1xBufferSizeInBytes(int, int, int, int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      get1xBufferSizeInBytes(int, int, int, int, int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      Returns the buffer size for playback at 1x speed.
      @@ -14877,12 +15046,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      getBufferingState() - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
       
      -
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, double) - Method in interface com.google.android.exoplayer2.audio.DefaultAudioSink.AudioTrackBufferSizeProvider
      +
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, int, double) - Method in interface com.google.android.exoplayer2.audio.DefaultAudioSink.AudioTrackBufferSizeProvider
      Returns the buffer size to use when creating an AudioTrack for a specific format and output mode.
      -
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, double) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      getBufferSizeInBytes(int, @com.google.android.exoplayer2.C.Encoding int, @com.google.android.exoplayer2.audio.DefaultAudioSink.OutputMode int, int, int, int, double) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
       
      getBuildConfig() - Static method in class com.google.android.exoplayer2.ext.vp9.VpxLibrary
      @@ -15184,6 +15353,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getConfigurationFormat() - Method in class com.google.android.exoplayer2.transformer.DefaultCodec
       
      +
      getConstant(long) - Static method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      +
      +
      Returns an instance that returns a constant value.
      +
      getContentBufferedPosition() - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      getContentBufferedPosition() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -15765,8 +15938,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getDecoderInfosSortedByFormatSupport(List<MediaCodecInfo>, Format) - Static method in class com.google.android.exoplayer2.mediacodec.MediaCodecUtil
      -
      Returns a copy of the provided decoder list sorted such that decoders with format support are - listed first.
      +
      Returns a copy of the provided decoder list sorted such that decoders with functional format + support are listed first.
      getDecoderSupport(@com.google.android.exoplayer2.RendererCapabilities.Capabilities int) - Static method in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -15934,6 +16107,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether downloads are currently paused.
      +
      getDrawable(Context, Resources, int) - Static method in class com.google.android.exoplayer2.util.Util
      +
      +
      Returns a Drawable for the given resource or throws a Resources.NotFoundException if not found.
      +
      getDrmUuid(String) - Static method in class com.google.android.exoplayer2.util.Util
      Derives a DRM UUID from drmScheme.
      @@ -16147,6 +16324,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns an ExtractorInput to read from the given input at given position.
      +
      getExtrapolating(long, float) - Static method in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      +
      +
      Returns an instance that extrapolates the provided position into the future.
      +
      getFallbackSelectionFor(LoadErrorHandlingPolicy.FallbackOptions, LoadErrorHandlingPolicy.LoadErrorInfo) - Method in class com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy
      Returns whether a loader should fall back to using another resource on encountering an error, @@ -16454,6 +16635,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      getInitialTimeline() - Method in class com.google.android.exoplayer2.source.LoopingMediaSource
      Deprecated.
      @@ -16981,6 +17164,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      getMediaItem() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      getMediaItem() - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -17025,7 +17210,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getMediaItemCount() - Method in interface com.google.android.exoplayer2.Player
      -
      Returns the number of media items in the playlist.
      +
      Returns the number of media items in the playlist.
      getMediaItemIndex() - Method in class com.google.android.exoplayer2.PlayerMessage
      @@ -17073,6 +17258,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getMediaPeriodIdForChildMediaPeriodId(MediaSource.MediaPeriodId, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.ads.AdsMediaSource
       
      +
      getMediaPeriodIdForChildMediaPeriodId(Integer, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      getMediaPeriodIdForChildMediaPeriodId(Integer, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.MergingMediaSource
       
      getMediaPeriodIdForChildMediaPeriodId(Void, MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.source.WrappingMediaSource
      @@ -17526,13 +17713,17 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the selected track overrides.
      +
      getPacketDurationUs(byte[]) - Static method in class com.google.android.exoplayer2.audio.OpusUtil
      +
      +
      Returns the duration of the given audio packet.
      +
      getParameters() - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector
       
      getParameters() - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      Returns the current parameters for track selection.
      -
      getPassthroughBufferSizeInBytes(@com.google.android.exoplayer2.C.Encoding int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      +
      getPassthroughBufferSizeInBytes(@com.google.android.exoplayer2.C.Encoding int, int) - Method in class com.google.android.exoplayer2.audio.DefaultAudioTrackBufferSizeProvider
      Returns the buffer size for passthrough playback.
      @@ -17685,6 +17876,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the number of pixels if this is a video format whose Format.width and Format.height are known, or Format.NO_VALUE otherwise
      +
      getPlaceholderMediaItemData(MediaItem) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Returns the placeholder SimpleBasePlayer.MediaItemData used for a new MediaItem added to the + playlist.
      +
      getPlaceholderState(SimpleBasePlayer.State) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      Returns the placeholder state used while a player method is handled asynchronously.
      @@ -17749,7 +17945,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      getPlaybackState() - Method in interface com.google.android.exoplayer2.Player
      -
      Returns the current playback state of the player.
      +
      Returns the current playback state of the player.
      getPlaybackState() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -19719,6 +19915,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns the window index in the composite source corresponding to the specified window index in a child source.
      +
      getWindowIndexForChildWindowIndex(Integer, int) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      getWindowIndexForChildWindowIndex(Void, int) - Method in class com.google.android.exoplayer2.source.WrappingMediaSource
       
      getWindowIndexForChildWindowIndex(T, int) - Method in class com.google.android.exoplayer2.source.CompositeMediaSource
      @@ -19899,6 +20097,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      H265SpsData(int, boolean, int, int, int[], int, int, int, int, float) - Constructor for class com.google.android.exoplayer2.util.NalUnitUtil.H265SpsData
       
      +
      handleAddMediaItems(int, List<MediaItem>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      handleBlockAddIDExtraData(MatroskaExtractor.Track, ExtractorInput, int) - Method in class com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor
       
      handleBlockAdditionalData(MatroskaExtractor.Track, int, ExtractorInput, int) - Method in class com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor
      @@ -19920,6 +20122,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      handleBuffer(ByteBuffer, long, int) - Method in class com.google.android.exoplayer2.testutil.CapturingAudioSink
       
      +
      handleClearVideoOutput(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to clear the video output.
      +
      +
      handleDecreaseDeviceVolume() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      handleDiscontinuity() - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Signals to the sink that the next buffer may be discontinuous with the previous buffer.
      @@ -19930,6 +20140,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      handleDiscontinuity() - Method in class com.google.android.exoplayer2.testutil.CapturingAudioSink
       
      +
      handleIncreaseDeviceVolume() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      handleInputBufferSupplementalData(DecoderInputBuffer) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecRenderer
      Handles supplemental data associated with an input buffer.
      @@ -19968,10 +20182,18 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Handles the message send to the component and additionally provides access to the player.
      +
      handleMoveMediaItems(int, int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      handlePendingSeek(ExtractorInput, PositionHolder) - Method in class com.google.android.exoplayer2.extractor.BinarySearchSeeker
      Continues to handle the pending seek operation.
      +
      handlePrepare() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.prepare().
      +
      handlePrepareComplete(AdsMediaSource, int, int) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader
       
      handlePrepareComplete(AdsMediaSource, int, int) - Method in interface com.google.android.exoplayer2.source.ads.AdsLoader
      @@ -19984,6 +20206,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Notifies the ads loader that the player was not able to prepare media for a given ad.
      +
      handleRelease() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.release().
      +
      +
      handleRemoveMediaItems(int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      HandlerWrapper - Interface in com.google.android.exoplayer2.util
      An interface to call through to a Handler.
      @@ -19992,11 +20222,59 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A message obtained from the handler.
      +
      handleSeek(int, long, @com.google.android.exoplayer2.Player.Command int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.seekTo(long) and other seek operations (for example, Player.seekToNext()).
      +
      +
      handleSetDeviceMuted(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.setDeviceVolume(int).
      +
      +
      handleSetMediaItems(List<MediaItem>, int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      handleSetPlayWhenReady(boolean) - Method in class com.google.android.exoplayer2.LegacyMediaPlayerWrapper
       
      handleSetPlayWhenReady(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      - + +
      +
      handleSetRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      + +
      +
      handleSetVideoOutput(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to set the video output.
      +
      +
      handleSetVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.setVolume(float).
      +
      +
      handleStop() - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      +
      Handles calls to Player.stop().
      HARDWARE_ACCELERATION_NOT_SUPPORTED - Static variable in interface com.google.android.exoplayer2.RendererCapabilities
      @@ -20194,6 +20472,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      hashCode() - Method in class com.google.android.exoplayer2.SeekParameters
       
      +
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
       
      +
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
       
      hashCode() - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State
       
      hashCode() - Method in class com.google.android.exoplayer2.source.ads.AdPlaybackState.AdGroup
      @@ -20420,6 +20702,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether all ads in the ad group at index adGroupIndex have been played, skipped or failed.
      +
      hasPositionDiscontinuity - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      Signals that a position discontinuity happened since the last update to the player.
      +
      hasPositiveStartOffset - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      Whether the HlsMediaPlaylist.startOffsetUs was explicitly defined by #EXT-X-START as a positive value @@ -21543,6 +21829,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      InternalFrame(String, String, String) - Constructor for class com.google.android.exoplayer2.metadata.id3.InternalFrame
       
      +
      intToStringMaxRadix(int) - Static method in class com.google.android.exoplayer2.util.Util
      +
      +
      Returns a string representation of the integer using radix value Character.MAX_RADIX.
      +
      invalidate() - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      Calls TrackSelector.InvalidationListener.onTrackSelectionsInvalidated() to invalidate all previously @@ -21698,6 +21988,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the track at the specified index in the selection is excluded.
      +
      isBrowsable - Variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      Optional boolean to indicate that the media is a browsable folder.
      +
      isCached - Variable in class com.google.android.exoplayer2.upstream.cache.CacheSpan
      Whether the CacheSpan is cached.
      @@ -21858,6 +22152,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the C.BUFFER_FLAG_DECODE_ONLY flag is set.
      +
      isDeviceMuted - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      Whether the device is muted.
      +
      isDeviceMuted() - Method in interface com.google.android.exoplayer2.ExoPlayer.DeviceComponent
      Deprecated. @@ -21886,6 +22184,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      isDone() - Method in class com.google.android.exoplayer2.util.RunnableFutureTask
       
      +
      isDynamic - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      Whether this media item may change over time, for example a moving live window.
      +
      isDynamic - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isDynamic - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -22039,9 +22341,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the given flag is set.
      +
      isFormatFunctionallySupported(Format) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecInfo
      +
      +
      Returns whether the decoder may functionally support decoding the given format.
      +
      isFormatSupported(Format) - Method in class com.google.android.exoplayer2.mediacodec.MediaCodecInfo
      -
      Returns whether the decoder may support decoding the given format.
      +
      Returns whether the decoder may support decoding the given format both functionally and + performantly.
      isFullyVisible() - Method in class com.google.android.exoplayer2.ui.StyledPlayerControlView
      @@ -22154,6 +22461,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      isLoadCompleted() - Method in class com.google.android.exoplayer2.testutil.FakeMediaChunk
       
      +
      isLoading - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      Whether the player is currently loading its source.
      +
      isLoading() - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      isLoading() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -22271,6 +22582,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the device can do passthrough playback for format.
      +
      isPlaceholder - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      Whether this media item contains placeholder information because the real information has yet + to be loaded.
      +
      +
      isPlaceholder - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
      +
      Whether this period contains placeholder information because the real information has yet to + be loaded.
      +
      isPlaceholder - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isPlaceholder - Variable in class com.google.android.exoplayer2.Timeline.Period
      @@ -22285,7 +22606,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      isPlayable - Variable in class com.google.android.exoplayer2.MediaMetadata
      -
      Optional boolean for media playability.
      +
      Optional boolean to indicate that the media is playable.
      isPlaying() - Method in class com.google.android.exoplayer2.BasePlayer
       
      @@ -22437,6 +22758,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the device supports secure placeholder surfaces.
      +
      isSeekable - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      Whether it's possible to seek within this media item.
      +
      isSeekable - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      isSeekable - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -22806,16 +23131,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Key request type for keys that will be used for online use.
      -
      keyForField(int) - Static method in exception com.google.android.exoplayer2.PlaybackException
      -
      -
      Converts the given field number to a string which can be used as a field key when implementing - PlaybackException.toBundle() and Bundleable.Creator.
      -
      -
      keyForField(int) - Static method in class com.google.android.exoplayer2.trackselection.TrackSelectionParameters
      -
      -
      Converts the given field number to a string which can be used as a field key when implementing - TrackSelectionParameters.toBundle() and Bundleable.Creator.
      -
      KeyRequest(byte[], String) - Constructor for class com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest
      @@ -23151,6 +23466,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The live playback configuration.
      +
      liveConfiguration - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The active MediaItem.LiveConfiguration, or null if the media item is not live.
      +
      liveConfiguration - Variable in class com.google.android.exoplayer2.Timeline.Window
      The MediaItem.LiveConfiguration that is used or null if Timeline.Window.isLive() returns @@ -23485,6 +23804,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The client manifest major version.
      +
      manifest - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The manifest of the media item, or null if not applicable.
      +
      manifest - Variable in class com.google.android.exoplayer2.Timeline.Window
      The manifest of the window.
      @@ -23604,6 +23927,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Holds data corresponding to a single track.
      +
      MAX_BYTES_PER_SECOND - Static variable in class com.google.android.exoplayer2.audio.OpusUtil
      +
      +
      Maximum achievable Opus bitrate.
      +
      MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY - Static variable in class com.google.android.exoplayer2.DefaultRenderersFactory
      The maximum number of frames that can be dropped between invocations of VideoRendererEventListener.onDroppedFrames(int, long).
      @@ -23723,6 +24050,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The maximum time spent during a single rebuffer, in milliseconds, or C.TIME_UNSET if no rebuffer occurred.
      +
      maxSeekToPreviousPositionMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, in + milliseconds.
      +
      maxValue(SparseLongArray) - Static method in class com.google.android.exoplayer2.util.Util
      Returns the maximum value in the given SparseLongArray.
      @@ -23955,6 +24287,156 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A seek to another media item has occurred.
      +
      MEDIA_TYPE_ALBUM - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items (e.g., music) belonging to an + album.
      +
      +
      MEDIA_TYPE_ARTIST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items (e.g., music) from the same + artist.
      +
      +
      MEDIA_TYPE_AUDIO_BOOK - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items forming an audio book.
      +
      +
      MEDIA_TYPE_AUDIO_BOOK_CHAPTER - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for an audio book chapter.
      +
      +
      MEDIA_TYPE_FOLDER_ALBUMS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing albums.
      +
      +
      MEDIA_TYPE_FOLDER_ARTISTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing artists.
      +
      +
      MEDIA_TYPE_FOLDER_AUDIO_BOOKS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing audio books.
      +
      +
      MEDIA_TYPE_FOLDER_GENRES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing genres.
      +
      +
      MEDIA_TYPE_FOLDER_MIXED - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder with mixed or undetermined content.
      +
      +
      MEDIA_TYPE_FOLDER_MOVIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing movies.
      +
      +
      MEDIA_TYPE_FOLDER_NEWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing news.
      +
      +
      MEDIA_TYPE_FOLDER_PLAYLISTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing playlists.
      +
      +
      MEDIA_TYPE_FOLDER_PODCASTS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing podcasts.
      +
      +
      MEDIA_TYPE_FOLDER_RADIO_STATIONS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing radio + stations.
      +
      +
      MEDIA_TYPE_FOLDER_TRAILERS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing movie trailers.
      +
      +
      MEDIA_TYPE_FOLDER_TV_CHANNELS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing TV channels.
      +
      +
      MEDIA_TYPE_FOLDER_TV_SERIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing TV series.
      +
      +
      MEDIA_TYPE_FOLDER_TV_SHOWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing TV shows.
      +
      +
      MEDIA_TYPE_FOLDER_VIDEOS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing videos.
      +
      +
      MEDIA_TYPE_FOLDER_YEARS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a folder containing years.
      +
      +
      MEDIA_TYPE_GENRE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items (e.g., music) of the same + genre.
      +
      +
      MEDIA_TYPE_MIXED - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      Media of undetermined type or a mix of multiple media types.
      +
      +
      MEDIA_TYPE_MOVIE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      +
      MEDIA_TYPE_MUSIC - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      +
      MEDIA_TYPE_NEWS - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      +
      MEDIA_TYPE_PLAYLIST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items (e.g., music) forming a + playlist.
      +
      +
      MEDIA_TYPE_PODCAST - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items belonging to a podcast.
      +
      +
      MEDIA_TYPE_PODCAST_EPISODE - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a podcast episode.
      +
      +
      MEDIA_TYPE_RADIO_STATION - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a radio station.
      +
      +
      MEDIA_TYPE_TRAILER - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a movie trailer.
      +
      +
      MEDIA_TYPE_TV_CHANNEL - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items that are part of a TV channel.
      +
      +
      MEDIA_TYPE_TV_SEASON - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      +
      +
      MEDIA_TYPE_TV_SERIES - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items that are part of a TV series.
      +
      +
      MEDIA_TYPE_TV_SHOW - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      +
      MEDIA_TYPE_VIDEO - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      +
      MEDIA_TYPE_YEAR - Static variable in class com.google.android.exoplayer2.MediaMetadata
      +
      +
      MediaMetadata.MediaType for a group of items (e.g., music) from the same + year.
      +
      MediaChunk - Class in com.google.android.exoplayer2.source.chunk
      An abstract base class for Chunks that contain media samples.
      @@ -24105,6 +24587,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media item, or null if the timeline is empty.
      +
      mediaItem - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      + +
      mediaItem - Variable in class com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition
       
      mediaItem - Variable in class com.google.android.exoplayer2.Timeline.Window
      @@ -24221,6 +24707,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media metadata.
      +
      mediaMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The MediaMetadata, including static data from the MediaItem and the media's Format, as well any dynamic metadata that + has been parsed from the media.
      +
      MediaMetadata - Class in com.google.android.exoplayer2
      Metadata of a MediaItem, playlist, or a combination of multiple sources of Metadata.
      @@ -24233,6 +24724,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The folder type of the media item.
      +
      MediaMetadata.MediaType - Annotation Type in com.google.android.exoplayer2
      +
      +
      The type of content described by the media item.
      +
      MediaMetadata.PictureType - Annotation Type in com.google.android.exoplayer2
      The picture type of the artwork.
      @@ -24478,6 +24973,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The media TrackGroup whose TrackSelectionOverride.trackIndices are forced to be selected.
      +
      mediaType - Variable in class com.google.android.exoplayer2.MediaMetadata
      +
      + +
      mediaUri - Variable in class com.google.android.exoplayer2.MediaItem.RequestMetadata
      The URI of the requested media, or null if not known or applicable.
      @@ -25295,6 +25794,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a newly created placeholder surface.
      +
      newlyRenderedFirstFrame - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      Whether a frame has been rendered for the first time since setting the surface, a rendering + reset, or since the stream being rendered was changed.
      +
      newMediaChunk(DefaultDashChunkSource.RepresentationHolder, DataSource, @com.google.android.exoplayer2.C.TrackType int, Format, @com.google.android.exoplayer2.C.SelectionReason int, Object, long, int, long, long) - Method in class com.google.android.exoplayer2.source.dash.DefaultDashChunkSource
       
      newNoDataInstance() - Static method in class com.google.android.exoplayer2.decoder.DecoderInputBuffer
      @@ -25718,7 +26222,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onAudioAttributesChanged(AudioAttributes) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the audio attributes change.
      +
      Called when the value of Player.getAudioAttributes() changes.
      onAudioCapabilitiesChanged(AudioCapabilities) - Method in interface com.google.android.exoplayer2.audio.AudioCapabilitiesReceiver.Listener
      @@ -25995,6 +26499,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called when the child source info has been refreshed.
      +
      onChildSourceInfoRefreshed(Integer, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      onChildSourceInfoRefreshed(Integer, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.source.MergingMediaSource
       
      onChildSourceInfoRefreshed(Void, MediaSource, Timeline) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource
      @@ -26141,7 +26647,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onCues(CueGroup) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when there is a change in the CueGroup.
      +
      Called when the value of Player.getCurrentCues() changes.
      onCues(CueGroup) - Method in interface com.google.android.exoplayer2.text.TextOutput
      @@ -26248,7 +26754,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onDeviceVolumeChanged(int, boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the device volume or mute state changes.
      +
      Called when the value of Player.getDeviceVolume() or Player.isDeviceMuted() changes.
      onDeviceVolumeChanged(AnalyticsListener.EventTime, int, boolean) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -26813,7 +27319,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onMediaMetadataChanged(MediaMetadata) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the combined MediaMetadata changes.
      +
      Called when the value of Player.getMediaMetadata() changes.
      onMessageArrived() - Method in interface com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget.Callback
      @@ -26910,7 +27416,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onPlaybackParametersChanged(PlaybackParameters) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the current playback parameters change.
      +
      Called when the value of Player.getPlaybackParameters() changes.
      onPlaybackSpeed(float) - Method in class com.google.android.exoplayer2.testutil.FakeTrackSelection
       
      @@ -27022,7 +27528,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onPlaylistMetadataChanged(MediaMetadata) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the playlist MediaMetadata changes.
      +
      Called when the value of Player.getPlaylistMetadata() changes.
      onPlayWhenReadyChanged(boolean) - Method in interface com.google.android.exoplayer2.trackselection.ExoTrackSelection
      @@ -27713,7 +28219,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onTimelineChanged(Timeline, @com.google.android.exoplayer2.Player.TimelineChangeReason int) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the timeline has been refreshed.
      +
      Called when the value of Player.getCurrentTimeline() changes.
      onTimelineChanged(Timeline, @com.google.android.exoplayer2.Player.TimelineChangeReason int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      @@ -27739,7 +28245,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onTracksChanged(Tracks) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the tracks change.
      +
      Called when the value of Player.getCurrentTracks() changes.
      onTrackSelectionChanged(boolean, Map<TrackGroup, TrackSelectionOverride>) - Method in interface com.google.android.exoplayer2.ui.TrackSelectionView.TrackSelectionListener
      @@ -28046,7 +28552,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onVolumeChanged(float) - Method in interface com.google.android.exoplayer2.Player.Listener
      -
      Called when the volume changes.
      +
      Called when the value of Player.getVolume() changes.
      onVolumeChanged(AnalyticsListener.EventTime, float) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -28630,6 +29136,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Parses the number of channels from the value attribute of an AudioChannelConfiguration with schemeIdUri "urn:mpeg:mpegB:cicp:ChannelConfiguration", as defined by ISO 23001-8 clause 8.1.
      +
      parsePacketAudioSampleCount(ByteBuffer) - Static method in class com.google.android.exoplayer2.audio.OpusUtil
      +
      +
      Returns the number of audio samples in the given audio packet.
      +
      parsePercentage(String) - Static method in class com.google.android.exoplayer2.text.webvtt.WebvttParserUtil
      Parses a percentage string.
      @@ -28702,6 +29212,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      parseText(XmlPullParser, String) - Static method in class com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
       
      +
      parseTileCountFromProperties(List<Descriptor>) - Method in class com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
      +
      +
      Parses given descriptors for thumbnail tile information.
      +
      parseTimestampUs(String) - Static method in class com.google.android.exoplayer2.text.webvtt.WebvttParserUtil
      Parses a WebVTT timestamp.
      @@ -28954,6 +29468,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The period index.
      +
      periods - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The list of periods in this media item, or an empty list to assume a + single period without ads and the same duration as the media item.
      +
      periodUid - Variable in class com.google.android.exoplayer2.Player.PositionInfo
      The UID of the period, or null if the timeline is empty.
      @@ -29264,6 +29783,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Class to capture output from a playback test.
      +
      playbackParameters - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The currently active PlaybackParameters.
      +
      PlaybackParameters - Class in com.google.android.exoplayer2
      Parameters that apply to playback, including speed setting.
      @@ -29298,6 +29821,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The playback state that became active.
      +
      playbackState - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The state of the player.
      +
      playbackStateHistory - Variable in class com.google.android.exoplayer2.analytics.PlaybackStats
      The playback state history as EventTimeAndPlaybackStates @@ -29332,6 +29859,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      A listener for PlaybackStats updates.
      +
      playbackSuppressionReason - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The reason why playback is suppressed even if SimpleBasePlayer.getPlayWhenReady() is true.
      +
      playbackType - Variable in class com.google.android.exoplayer2.DeviceInfo
      The type of playback.
      @@ -29354,11 +29885,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Player.Command - Annotation Type in com.google.android.exoplayer2
      -
      Commands that can be executed on a Player.
      +
      Commands that indicate which method calls are currently permitted on a particular + Player instance.
      Player.Commands - Class in com.google.android.exoplayer2
      -
      A set of commands.
      +
      A set of commands.
      Player.Commands.Builder - Class in com.google.android.exoplayer2
      @@ -29374,11 +29906,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Player.Events - Class in com.google.android.exoplayer2
      -
      A set of events.
      +
      A set of events.
      Player.Listener - Interface in com.google.android.exoplayer2
      -
      Listener of all changes in the Player.
      +
      Listener for changes in a Player.
      Player.MediaItemTransitionReason - Annotation Type in com.google.android.exoplayer2
      @@ -29442,6 +29974,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Handles emsg messages for a specific track for the player.
      +
      playerError - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The last error that caused playback to fail, or null if there was no error.
      +
      PlayerId - Class in com.google.android.exoplayer2.analytics
      Identifier for a player instance.
      @@ -29531,12 +30067,20 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Determines when the buffering view is shown.
      +
      playlist - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The media items in the playlist.
      +
      PLAYLIST_TYPE_EVENT - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      PLAYLIST_TYPE_UNKNOWN - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      PLAYLIST_TYPE_VOD - Static variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
       
      +
      playlistMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The playlist MediaMetadata.
      +
      PlaylistResetException(Uri) - Constructor for exception com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker.PlaylistResetException
      Creates an instance.
      @@ -29646,10 +30190,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      populateMediaMetadata(MediaMetadata.Builder) - Method in class com.google.android.exoplayer2.metadata.id3.ApicFrame
       
      populateMediaMetadata(MediaMetadata.Builder) - Method in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
       
      +
      +
      Uses the first element in TextInformationFrame.values to set the relevant field in MediaMetadata + (as determined by Id3Frame.id).
      +
      populateMediaMetadata(MediaMetadata.Builder) - Method in interface com.google.android.exoplayer2.metadata.Metadata.Entry
      -
      Updates the MediaMetadata.Builder with the type specific values stored in this Entry.
      +
      Updates the MediaMetadata.Builder with the type-specific values stored in this + Entry.
      position - Variable in class com.google.android.exoplayer2.extractor.PositionHolder
      @@ -29710,12 +30258,21 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The cue box anchor positioned by Cue.position.
      +
      positionDiscontinuityReason - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The reason for the last position discontinuity.
      +
      PositionHolder - Class in com.google.android.exoplayer2.extractor
      Holds a position in the stream.
      PositionHolder() - Constructor for class com.google.android.exoplayer2.extractor.PositionHolder
       
      +
      positionInFirstPeriodUs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The position of the start of this media item relative to the start of the first period + belonging to it, in microseconds.
      +
      positionInFirstPeriodUs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The position of the start of this window relative to the start of the first period belonging @@ -29951,6 +30508,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      prepareSourceInternal(TransferListener) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -29985,6 +30544,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Strategies controlling the layout of input pixels in the output frame.
      +
      presentationStartTimeMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The start time of the live presentation, in milliseconds since the Unix epoch, or C.TIME_UNSET if unknown or not applicable.
      +
      presentationStartTimeMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The start time of the presentation to which this window belongs in milliseconds since the @@ -30816,7 +31379,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      readLine() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      -
      Reads a line of text.
      +
      Reads a line of text in UTF-8.
      +
      +
      readLine(Charset) - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      +
      +
      Reads a line of text in charset.
      readLittleEndianInt() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      @@ -30944,6 +31511,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Reads a long value encoded by UTF-8 encoding
      +
      readUtfCharsetFromBom() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      +
      +
      Reads a UTF byte order mark (BOM) and returns the UTF Charset it represents.
      +
      readVorbisCommentHeader(ParsableByteArray) - Static method in class com.google.android.exoplayer2.extractor.VorbisUtil
      Reads a Vorbis comment header.
      @@ -31530,6 +32101,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      releasePeriod(MediaPeriod) - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -31592,6 +32165,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource
       
      +
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2
      +
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.dash.DashMediaSource
       
      releaseSourceInternal() - Method in class com.google.android.exoplayer2.source.hls.HlsMediaSource
      @@ -31660,7 +32235,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeAll(@com.google.android.exoplayer2.Player.Command int...) - Method in class com.google.android.exoplayer2.Player.Commands.Builder
      -
      Removes commands.
      +
      Removes commands.
      removeAll(int...) - Method in class com.google.android.exoplayer2.util.FlagSet.Builder
      @@ -32110,9 +32685,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      "Repeat One" button enabled.
      -
      repeatCurrentMediaItem() - Method in class com.google.android.exoplayer2.BasePlayer
      +
      repeatMode - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      -
      Repeat the current media item.
      +
      The Player.RepeatMode used for playback.
      RepeatModeActionProvider - Class in com.google.android.exoplayer2.ext.mediasession
      @@ -32989,6 +33564,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Runs tasks of the main Looper until a player error occurs.
      +
      runUntilIsLoading(Player, boolean) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      +
      +
      Runs tasks of the main Looper until Player.isLoading() matches the expected + value or a playback error occurs.
      +
      runUntilPendingCommandsAreFullyHandled(ExoPlayer) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      Runs tasks of the main Looper until the player completely handled all previously issued @@ -33487,6 +34067,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks back in the current MediaItem by Player.getSeekBackIncrement() milliseconds.
      +
      seekBackIncrementMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The Player.seekBack() increment in milliseconds.
      +
      seekForward() - Method in class com.google.android.exoplayer2.BasePlayer
       
      seekForward() - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -33498,6 +34082,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks forward in the current MediaItem by Player.getSeekForwardIncrement() milliseconds.
      +
      seekForwardIncrementMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The Player.seekForward() increment in milliseconds.
      +
      seekMap - Variable in class com.google.android.exoplayer2.extractor.BinarySearchSeeker
       
      seekMap - Variable in class com.google.android.exoplayer2.testutil.FakeExtractorOutput
      @@ -33558,7 +34146,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Attempts to seek the read position to the specified sample index.
      -
      seekTo(int, long) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      +
      seekTo(int, long) - Method in class com.google.android.exoplayer2.BasePlayer
       
      seekTo(int, long) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -33568,13 +34156,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Seeks to a position specified in milliseconds in the specified MediaItem.
      -
      seekTo(int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.BasePlayer
      +
      +
      Seeks to a position in the specified MediaItem.
      +
      +
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      -
      seekTo(int, long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      +
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
       
      +
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
       
      -
      seekTo(int, long) - Method in class com.google.android.exoplayer2.testutil.StubPlayer
      +
      seekTo(int, long, @com.google.android.exoplayer2.Player.Command int, boolean) - Method in class com.google.android.exoplayer2.testutil.StubPlayer
       
      seekTo(long) - Method in class com.google.android.exoplayer2.BasePlayer
       
      @@ -34238,6 +34832,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an ActionSchedule to be run by the test runner.
      +
      setAdBufferedPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently + playing ad is buffered, in milliseconds.
      +
      setAdErrorListener(AdErrorEvent.AdErrorListener) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader.Builder
      Sets a listener for ad errors that will be passed to AdsLoader.addAdErrorListener(AdErrorListener) and @@ -34269,10 +34868,22 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the MIME types to prioritize for linear ad media.
      +
      setAdPlaybackState(AdPlaybackState) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      +
      Sets the AdPlaybackState.
      +
      setAdPlaybackStates(ImmutableMap<Object, AdPlaybackState>) - Method in class com.google.android.exoplayer2.source.ads.ServerSideAdInsertionMediaSource
      Sets the map of ad playback states published by this source.
      +
      setAdPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current ad playback position in milliseconds.
      +
      +
      setAdPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the SimpleBasePlayer.PositionSupplier for the current ad playback position in milliseconds.
      +
      setAdPreloadTimeoutMs(long) - Method in class com.google.android.exoplayer2.ext.ima.ImaAdsLoader.Builder
      Sets the duration in milliseconds for which the player must buffer while preloading an ad @@ -34557,6 +35168,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.audio.ForwardingAudioSink
       
      +
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current AudioAttributes.
      +
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector
       
      setAudioAttributes(AudioAttributes) - Method in class com.google.android.exoplayer2.trackselection.TrackSelector
      @@ -34986,6 +35601,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the content of the output buffer, consisting of a Subtitle and associated metadata.
      +
      setContentBufferedPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated position up to which the currently + playing content is buffered, in milliseconds.
      +
      setContentLength(long) - Method in class com.google.android.exoplayer2.testutil.DownloadBuilder
       
      setContentLength(ContentMetadataMutations, long) - Static method in class com.google.android.exoplayer2.upstream.cache.ContentMetadataMutations
      @@ -34993,6 +35613,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Adds a mutation to set the ContentMetadata.KEY_CONTENT_LENGTH value, or to remove any existing value if C.LENGTH_UNSET is passed.
      +
      setContentPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current content playback position in milliseconds.
      +
      +
      setContentPositionMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the SimpleBasePlayer.PositionSupplier for the current content playback position in + milliseconds.
      +
      setContentSourceId(String) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionUriBuilder
      The stream request content source ID used for on-demand streams.
      @@ -35116,6 +35745,18 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the cues to be displayed by the view.
      +
      setCurrentAd(int, int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current ad indices, or C.INDEX_UNSET if no ad is playing.
      +
      +
      setCurrentCues(CueGroup) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current cues.
      +
      +
      setCurrentMediaItemIndex(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current media item index.
      +
      setCurrentPosition(long) - Method in class com.google.android.exoplayer2.source.mediaparser.InputReaderAdapterV30
      Sets the absolute position in the resource from which the wrapped DataReader reads.
      @@ -35241,6 +35882,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the default artwork to display if useArtwork is true and no artwork is present in the media.
      +
      setDefaultPositionUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the default position relative to the start of the media item at which to begin + playback, in microseconds.
      +
      setDefaultRequestProperties(Map<String, String>) - Method in class com.google.android.exoplayer2.ext.cronet.CronetDataSource.Factory
       
      setDefaultRequestProperties(Map<String, String>) - Method in class com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource.Factory
      @@ -35283,6 +35929,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an optional, detailed reason that the view is on top of the player.
      +
      setDeviceInfo(DeviceInfo) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the DeviceInfo.
      +
      setDeviceMuted(boolean) - Method in interface com.google.android.exoplayer2.ExoPlayer.DeviceComponent
      Deprecated. @@ -35329,6 +35979,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current device volume.
      +
      setDeviceVolume(int) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -35510,10 +36164,23 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the duration of the video in milliseconds.
      +
      setDurationUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the duration of the media item, in microseconds.
      +
      +
      setDurationUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      +
      Sets the total duration of the period, in microseconds, or C.TIME_UNSET if unknown.
      +
      setDurationUs(long) - Method in class com.google.android.exoplayer2.source.SilenceMediaSource.Factory
      Sets the duration of the silent audio.
      +
      setElapsedRealtimeEpochOffsetMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the offset between SystemClock.elapsedRealtime() and the time since the Unix + epoch according to the clock of the media origin server.
      +
      setEnableAudioFloatOutput(boolean) - Method in class com.google.android.exoplayer2.DefaultRenderersFactory
      Sets whether floating point audio should be output when possible.
      @@ -35799,6 +36466,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets whether to focus the skip button (when available) on Android TV devices.
      +
      setFocusSkipButtonWhenAvailable(boolean) - Method in class com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.Builder
      +
      +
      Sets whether to focus the skip button (when available) on Android TV devices.
      +
      setFolderType(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      @@ -36068,16 +36739,46 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an int type uniform.
      +
      setIsBrowsable(Boolean) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      +
      +
      Sets whether the media is a browsable folder.
      +
      +
      setIsDeviceMuted(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets whether the device is muted.
      +
      setIsDisabled(boolean) - Method in class com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder
      Sets whether the selection is initially shown as disabled.
      +
      setIsDynamic(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets whether this media item may change over time, for example a moving live window.
      +
      +
      setIsLoading(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets whether the player is currently loading its source.
      +
      setIsNetwork(boolean) - Method in class com.google.android.exoplayer2.testutil.FakeDataSource.Factory
       
      +
      setIsPlaceholder(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets whether this media item contains placeholder information because the real information + has yet to be loaded.
      +
      +
      setIsPlaceholder(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      +
      Sets whether this period contains placeholder information because the real information has + yet to be loaded
      +
      setIsPlayable(Boolean) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets whether the media is playable.
      +
      setIsSeekable(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets whether it's possible to seek within this media item.
      +
      setItalic(boolean) - Method in class com.google.android.exoplayer2.text.webvtt.WebvttCssStyle
       
      setKeepContentOnPlayerReset(boolean) - Method in class com.google.android.exoplayer2.ui.PlayerView
      @@ -36243,6 +36944,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      +
      setLiveConfiguration(MediaItem.LiveConfiguration) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the active MediaItem.LiveConfiguration, or null if the media item is not live.
      +
      setLiveMaxOffsetMs(long) - Method in class com.google.android.exoplayer2.MediaItem.Builder
      Deprecated. @@ -36403,6 +37108,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Looper that must be used for all calls to the transformer and that is used to call listeners on.
      +
      setManifest(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the manifest of the media item.
      +
      setManifest(Object) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder
      Sets a manifest to be used by a FakeMediaSource in the test runner.
      @@ -36485,6 +37194,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the maximum playback speed.
      +
      setMaxSeekToPreviousPositionMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the maximum position for which BasePlayer.seekToPrevious() seeks to the previous item, + in milliseconds.
      +
      setMaxVideoBitrate(int) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters.Builder
       
      setMaxVideoBitrate(int) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder
      @@ -36553,6 +37267,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Clears the playlist, adds the specified MediaItem and resets the position to the default position.
      +
      setMediaItem(MediaItem) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the MediaItem.
      +
      +
      setMediaItem(MediaItem) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      +
      Sets the MediaItem to be used for the concatenated media source.
      +
      setMediaItem(MediaItem, boolean) - Method in class com.google.android.exoplayer2.BasePlayer
       
      setMediaItem(MediaItem, boolean) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -36585,8 +37307,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist, adds the specified MediaItems and resets the position to - the default position.
      +
      Clears the playlist, adds the specified media items and resets the + position to the default position.
      setMediaItems(List<MediaItem>, boolean) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      @@ -36596,7 +37318,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>, boolean) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      setMediaItems(List<MediaItem>, boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -36614,7 +37336,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setMediaItems(List<MediaItem>, int, long) - Method in interface com.google.android.exoplayer2.Player
      -
      Clears the playlist and adds the specified MediaItems.
      +
      Clears the playlist and adds the specified media items.
      setMediaItems(List<MediaItem>, int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      @@ -36636,6 +37358,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the media metadata.
      +
      setMediaMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the MediaMetadata.
      +
      setMediaMetadataProvider(MediaSessionConnector.MediaMetadataProvider) - Method in class com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
      Sets a provider of metadata to be published to the media session.
      @@ -36689,6 +37415,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      +
      setMediaSourceFactory(MediaSource.Factory) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      + +
      setMediaSourceFactory(MediaSource.Factory) - Method in class com.google.android.exoplayer2.testutil.TestExoPlayerBuilder
      Sets the MediaSource.Factory to be used by the player.
      @@ -36744,6 +37475,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));  
      setMediaSources(List<MediaSource>, int, long) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      +
      setMediaType(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      +
      + +
      setMediaUri(Uri) - Method in class com.google.android.exoplayer2.MediaItem.RequestMetadata.Builder
      Sets the URI of the requested media, or null if not known or applicable.
      @@ -36870,6 +37605,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Overrides the network type.
      +
      setNewlyRenderedFirstFrame(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets whether a frame has been rendered for the first time since setting the surface, a + rendering reset, or since the stream being rendered was changed.
      +
      setNewSourceInfo(Timeline) - Method in class com.google.android.exoplayer2.testutil.FakeMediaSource
      Sets a new timeline.
      @@ -37117,6 +37857,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPercentDownloaded(float) - Method in class com.google.android.exoplayer2.testutil.DownloadBuilder
       
      +
      setPeriods(List<SimpleBasePlayer.PeriodData>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the list of periods in this media item.
      +
      setPitch(float) - Method in class com.google.android.exoplayer2.audio.SonicAudioProcessor
      Sets the target playback pitch.
      @@ -37133,6 +37877,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets whether to play an ad before the start position when beginning playback.
      +
      setPlaybackLooper(Looper) - Method in class com.google.android.exoplayer2.ExoPlayer.Builder
      +
      +
      Sets the Looper that will be used for playback.
      +
      setPlaybackParameters(PlaybackParameters) - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Attempts to set the playback parameters.
      @@ -37157,6 +37905,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the currently active PlaybackParameters.
      +
      setPlaybackParameters(PlaybackParameters) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -37201,6 +37953,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaybackSpeed(float, float) - Method in class com.google.android.exoplayer2.video.MediaCodecVideoRenderer
       
      +
      setPlaybackState(@com.google.android.exoplayer2.Player.State int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the state of the player.
      +
      +
      setPlaybackSuppressionReason(@com.google.android.exoplayer2.Player.PlaybackSuppressionReason int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the reason why playback is suppressed even if SimpleBasePlayer.getPlayWhenReady() is true.
      +
      setPlayClearContentWithoutKey(boolean) - Method in class com.google.android.exoplayer2.MediaItem.DrmConfiguration.Builder
      Sets whether clear samples within protected content should be played when keys for the @@ -37266,6 +38026,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlayer(Player, Looper) - Method in class com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
       
      +
      setPlayerError(PlaybackException) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets last error that caused playback to fail, or null if there was no error.
      +
      setPlayerId(PlayerId) - Method in interface com.google.android.exoplayer2.audio.AudioSink
      Sets the PlayerId of the player using this audio sink.
      @@ -37285,6 +38049,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets an Player.Listener to be registered to listen to player events.
      setPlaylist(List<MediaItem>, MediaMetadata) - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
      +
      setPlaylist(List<SimpleBasePlayer.MediaItemData>) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the list of media items in the playlist.
      +
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      This method is not supported and does nothing.
      @@ -37299,6 +38067,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the playlist MediaMetadata.
      +
      setPlaylistMetadata(MediaMetadata) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -37380,6 +38152,16 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the cue box anchor positioned by position.
      +
      setPositionDiscontinuity(@com.google.android.exoplayer2.Player.DiscontinuityReason int, long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Signals that a position discontinuity happened since the last player update and sets the + reason for it.
      +
      +
      setPositionInFirstPeriodUs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the position of the start of this media item relative to the start of the first period + belonging to it, in microseconds.
      +
      setPositionUs(long) - Method in class com.google.android.exoplayer2.text.ExoplayerCuesDecoder
       
      setPositionUs(long) - Method in class com.google.android.exoplayer2.text.SimpleSubtitleDecoder
      @@ -37539,6 +38321,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets a listener for preparation events.
      +
      setPresentationStartTimeMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the start time of the live presentation.
      +
      setPreviousActionIconResourceId(int) - Method in class com.google.android.exoplayer2.ui.PlayerNotificationManager.Builder
      The resource id of the drawable to be used as the icon of action PlayerNotificationManager.ACTION_PREVIOUS.
      @@ -37751,6 +38537,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.RepeatMode to be used for playback.
      +
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      +
       
      +
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the Player.RepeatMode used for playback.
      +
      setRepeatMode(@com.google.android.exoplayer2.Player.RepeatMode int) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -37763,8 +38555,6 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      setRepeatMode(int) - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
       
      -
      setRepeatMode(int) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
      -
       
      SetRepeatMode(String, @com.google.android.exoplayer2.Player.RepeatMode int) - Constructor for class com.google.android.exoplayer2.testutil.Action.SetRepeatMode
       
      setRepeatToggleModes(@com.google.android.exoplayer2.util.RepeatModeUtil.RepeatToggleModes int) - Method in class com.google.android.exoplayer2.ui.PlayerControlView
      @@ -37931,6 +38721,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.seekBack() increment.
      +
      setSeekBackIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the Player.seekBack() increment in milliseconds.
      +
      setSeekBackIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer.Builder
      Deprecated. @@ -37945,6 +38739,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the Player.seekForward() increment.
      +
      setSeekForwardIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the Player.seekForward() increment in milliseconds.
      +
      setSeekForwardIncrementMs(long) - Method in class com.google.android.exoplayer2.SimpleExoPlayer.Builder
      Deprecated. @@ -38206,6 +39004,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets whether shuffling of media items is enabled.
      +
      setShuffleModeEnabled(boolean) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -38436,6 +39238,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets a list of Formats to be used by a FakeMediaSource to create media periods.
      +
      setSurfaceSize(Size) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the size of the surface onto which the video is being rendered.
      +
      setTag(Object) - Method in class com.google.android.exoplayer2.MediaItem.Builder
      Sets the optional tag for custom attributes.
      @@ -38495,6 +39301,26 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the resource ID of the theme used to inflate this dialog.
      +
      setThrowsWhenUsingWrongThread(boolean) - Method in class com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
      +
      +
      Deprecated. +
      Do not use this method and ensure all calls are made from the correct thread.
      +
      +
      +
      setThrowsWhenUsingWrongThread(boolean) - Method in class com.google.android.exoplayer2.util.ListenerSet
      +
      +
      Deprecated. +
      Do not use this method and ensure all calls are made from the correct thread.
      +
      +
      +
      setTileCountHorizontal(int) - Method in class com.google.android.exoplayer2.Format.Builder
      +
      + +
      +
      setTileCountVertical(int) - Method in class com.google.android.exoplayer2.Format.Builder
      +
      + +
      setTimeBarMinUpdateInterval(int) - Method in class com.google.android.exoplayer2.ui.PlayerControlView
      Sets the minimum interval between time bar position updates.
      @@ -38503,6 +39329,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the minimum interval between time bar position updates.
      +
      setTimedMetadata(Metadata) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the most recent timed Metadata.
      +
      setTimeline(Timeline) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder
      Sets a Timeline to be used by a FakeMediaSource in the test runner.
      @@ -38527,6 +39357,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the input matrix to an identity matrix.
      +
      setTotalBufferedDurationMs(SimpleBasePlayer.PositionSupplier) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the SimpleBasePlayer.PositionSupplier for the estimated total buffered duration in + milliseconds.
      +
      setTotalDiscCount(Integer) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets the total number of discs.
      @@ -38562,6 +39397,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the track number.
      +
      setTracks(Tracks) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the Tracks of this media item.
      +
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
       
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.ForwardingPlayer
      @@ -38574,6 +39413,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the currently active TrackSelectionParameters.
      +
      setTrackSelectionParameters(TrackSelectionParameters) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -38653,6 +39496,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the number of bytes searched to find a timestamp for TsExtractor instances created by the factory.
      +
      setTsSubtitleFormats(List<Format>) - Method in class com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
      +
      +
      Sets a list of subtitle formats to pass to the DefaultTsPayloadReaderFactory used by + TsExtractor instances created by the factory.
      +
      setTunnelingEnabled(boolean) - Method in class com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters.Builder
      Sets whether to enable tunneling if possible.
      @@ -38666,6 +39514,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the message type forwarded to PlayerMessage.Target.handleMessage(int, Object).
      +
      setUid(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the unique identifier of this media item within a playlist.
      +
      +
      setUid(Object) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData.Builder
      +
      +
      Sets the unique identifier of the period within its media item.
      +
      setUnderline(boolean) - Method in class com.google.android.exoplayer2.text.webvtt.WebvttCssStyle
       
      setUnplayedColor(int) - Method in class com.google.android.exoplayer2.ui.DefaultTimeBar
      @@ -38974,6 +39830,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));  
      setVideoScalingMode(int) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      +
      setVideoSize(VideoSize) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current video size.
      +
      setVideoSurface() - Method in class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
      Schedules a set video surface action.
      @@ -39155,6 +40015,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer
       
      +
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.State.Builder
      +
      +
      Sets the current audio volume, with 0 being silence and 1 being unity gain (signal + unchanged).
      +
      setVolume(float) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      Deprecated.
      @@ -39197,6 +40062,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the fill color of the window.
      +
      setWindowStartTimeMs(long) - Method in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData.Builder
      +
      +
      Sets the start time of the live window.
      +
      setWriter(CharSequence) - Method in class com.google.android.exoplayer2.MediaMetadata.Builder
      Sets the writer.
      @@ -39360,6 +40229,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Shows the scrubber handle with animation.
      +
      shuffleModeEnabled - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      Whether shuffling of media items is enabled.
      +
      ShuffleOrder - Interface in com.google.android.exoplayer2.source
      Shuffled order of indices.
      @@ -39428,6 +40301,28 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates the base class.
      +
      SimpleBasePlayer.MediaItemData - Class in com.google.android.exoplayer2
      +
      +
      An immutable description of an item in the playlist, containing both static setup information + like MediaItem and dynamic data that is generally read from the media like the + duration.
      +
      +
      SimpleBasePlayer.MediaItemData.Builder - Class in com.google.android.exoplayer2
      +
      +
      A builder for SimpleBasePlayer.MediaItemData objects.
      +
      +
      SimpleBasePlayer.PeriodData - Class in com.google.android.exoplayer2
      +
      +
      Data describing the properties of a period inside a SimpleBasePlayer.MediaItemData.
      +
      +
      SimpleBasePlayer.PeriodData.Builder - Class in com.google.android.exoplayer2
      +
      +
      A builder for SimpleBasePlayer.PeriodData objects.
      +
      +
      SimpleBasePlayer.PositionSupplier - Interface in com.google.android.exoplayer2
      +
      +
      A supplier for a position.
      +
      SimpleBasePlayer.State - Class in com.google.android.exoplayer2
      An immutable state description of the player.
      @@ -41042,6 +41937,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a new instance.
      +
      surfaceSize - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The size of the surface onto which the video is being rendered.
      +
      svcTemporalLayerCount - Variable in class com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry
      The number of layers in the SVC extended frames.
      @@ -41230,6 +42129,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Text information ID3 frame.
      TextInformationFrame(String, String, String) - Constructor for class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      +
      +
      Deprecated. +
      Use TextInformationFrame(String id, String description, String[] values + instead
      +
      +
      +
      TextInformationFrame(String, String, List<String>) - Constructor for class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
       
      TextOutput - Interface in com.google.android.exoplayer2.text
      @@ -41289,6 +42195,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a rated instance.
      +
      tileCountHorizontal - Variable in class com.google.android.exoplayer2.Format
      +
      +
      The number of horizontal tiles in an image, or Format.NO_VALUE if not known or applicable.
      +
      +
      tileCountVertical - Variable in class com.google.android.exoplayer2.Format
      +
      +
      The number of vertical tiles in an image, or Format.NO_VALUE if not known or applicable.
      +
      TIME_END_OF_SOURCE - Static variable in class com.google.android.exoplayer2.C
      Special constant representing a time corresponding to the end of a source.
      @@ -41306,6 +42220,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Listener for scrubbing events.
      +
      timedMetadata - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The most recent timed metadata.
      +
      TimedValueQueue<V> - Class in com.google.android.exoplayer2.util
      A utility class to keep a queue of values with timestamps.
      @@ -41324,6 +42242,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The Timeline in which the seek was attempted.
      +
      timeline - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      + +
      timeline - Variable in class com.google.android.exoplayer2.source.ForwardingTimeline
       
      Timeline - Class in com.google.android.exoplayer2
      @@ -41643,7 +42565,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns a Bundle representing the information stored in this object.
      -
      toBundle(boolean) - Method in class com.google.android.exoplayer2.Timeline
      +
      toBundle(boolean, boolean) - Method in class com.google.android.exoplayer2.Player.PositionInfo
      +
      +
      Returns a Bundle representing the information stored in this object, filtered by + available commands.
      +
      toBundleArrayList(Collection<T>) - Static method in class com.google.android.exoplayer2.util.BundleableUtil
      Converts a collection of Bundleable to an ArrayList of Bundle so that @@ -41658,6 +42584,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      +
      toBundleWithOneWindowOnly(int) - Method in class com.google.android.exoplayer2.Timeline
      +
      +
      Returns a Bundle containing just the specified Timeline.Window.
      +
      toByteArray(InputStream) - Static method in class com.google.android.exoplayer2.util.Util
      Converts the entirety of an InputStream to a byte array.
      @@ -41842,6 +42772,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Total buffered duration from AnalyticsListener.EventTime.currentPlaybackPositionMs at the time of the event, in milliseconds.
      +
      totalBufferedDurationMsSupplier - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The SimpleBasePlayer.PositionSupplier for the estimated total buffered duration in milliseconds.
      +
      totalDiscCount - Variable in class com.google.android.exoplayer2.MediaMetadata
      Optional total number of discs.
      @@ -42070,6 +43004,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      trackOutputs - Variable in class com.google.android.exoplayer2.testutil.FakeExtractorOutput
       
      +
      tracks - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The Tracks of this media item.
      +
      tracks - Variable in class com.google.android.exoplayer2.trackselection.BaseTrackSelection
      The indices of the selected tracks in BaseTrackSelection.group, in order of decreasing bandwidth.
      @@ -42149,6 +43087,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Constructs an instance to force trackIndices in trackGroup to be selected.
      +
      trackSelectionParameters - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The currently active TrackSelectionParameters.
      +
      TrackSelectionParameters - Class in com.google.android.exoplayer2.trackselection
      Parameters for controlling track selection.
      @@ -42678,6 +43620,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Creates a UdpDataSourceException.
      +
      uid - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The unique identifier of this media item.
      +
      +
      uid - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.PeriodData
      +
      +
      The unique identifier of the period within its media item.
      +
      uid - Variable in class com.google.android.exoplayer2.Timeline.Period
      A unique identifier for the period.
      @@ -43099,6 +44049,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      useBoundedDataSpecFor(String) - Method in class com.google.android.exoplayer2.testutil.CacheAsserts.RequestSet
       
      +
      useDefaultMediaSourceFactory(Context) - Method in class com.google.android.exoplayer2.source.ConcatenatingMediaSource2.Builder
      +
      + +
      USER_DATA_IDENTIFIER_GA94 - Static variable in class com.google.android.exoplayer2.extractor.CeaUtil
       
      USER_DATA_TYPE_CODE_MPEG_CC - Static variable in class com.google.android.exoplayer2.extractor.CeaUtil
      @@ -43211,7 +44165,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The value.
      value - Variable in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      -
       
      +
      +
      Deprecated. +
      Use the first element of TextInformationFrame.values instead.
      +
      +
      value - Variable in class com.google.android.exoplayer2.metadata.mp4.MdtaMetadataEntry
      The payload.
      @@ -43226,6 +44184,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      value - Variable in class com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement
       
      +
      values - Variable in class com.google.android.exoplayer2.metadata.id3.TextInformationFrame
      +
      +
      The text values of this frame.
      +
      variableDefinitions - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist
      Contains variable definitions, as defined by the #EXT-X-DEFINE tag.
      @@ -43489,6 +44451,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The size of the video data, in bytes.
      +
      videoSize - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current video size.
      +
      VideoSize - Class in com.google.android.exoplayer2.video
      Represents the video size.
      @@ -43533,6 +44499,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Viewport width in pixels.
      +
      volume - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.State
      +
      +
      The current audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
      +
      VorbisBitArray - Class in com.google.android.exoplayer2.extractor
      Wraps a byte array, providing methods that allow it to be read as a Vorbis bitstream.
      @@ -43877,6 +44847,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The sequence number of the window in the buffered sequence of windows this media period is part of.
      +
      windowStartTimeMs - Variable in class com.google.android.exoplayer2.SimpleBasePlayer.MediaItemData
      +
      +
      The start time of the live window, in milliseconds since the Unix epoch, or C.TIME_UNSET if unknown or not applicable.
      +
      windowStartTimeMs - Variable in class com.google.android.exoplayer2.Timeline.Window
      The window's start time in milliseconds since the Unix epoch, or C.TIME_UNSET if @@ -44258,7 +45232,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      yuvStrides - Variable in class com.google.android.exoplayer2.decoder.VideoDecoderOutputBuffer
       
      -A B C D E F G H I J K L M N O P Q R S T U V W X Y 
      All Classes All Packages + + + +

      Z

      +
      +
      ZERO - Static variable in interface com.google.android.exoplayer2.SimpleBasePlayer.PositionSupplier
      +
      +
      An instance returning a constant position of zero.
      +
      +
      ZERO - Static variable in class com.google.android.exoplayer2.util.Size
      +
       
      +
      +A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
      All Classes All Packages