From 0831f6cab36d8607fa63a6dcac5a17abead75c90 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 17 Aug 2018 08:26:41 -0700 Subject: [PATCH 01/42] Remove deprecated usage of MediaSession APIs ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209152304 --- .../google/android/exoplayer2/ext/cast/CastPlayer.java | 1 - .../ext/mediasession/MediaSessionConnector.java | 9 --------- .../exoplayer2/ext/mediasession/TimelineQueueEditor.java | 9 ++------- 3 files changed, 2 insertions(+), 17 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 21e853dd62..f3e66315b4 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 @@ -839,7 +839,6 @@ public final class CastPlayer implements Player { @Override public void onAdBreakStatusUpdated() {} - // SessionManagerListener implementation. @Override diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index c3d6a13f46..f1d3e8fbd0 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -248,8 +248,6 @@ public final class MediaSessionConnector { * description)}. */ void onRemoveQueueItem(Player player, MediaDescriptionCompat description); - /** See {@link MediaSessionCompat.Callback#onRemoveQueueItemAt(int index)}. */ - void onRemoveQueueItemAt(Player player, int index); } /** Callback receiving a user rating for the active media item. */ @@ -1022,12 +1020,5 @@ public final class MediaSessionConnector { queueEditor.onRemoveQueueItem(player, description); } } - - @Override - public void onRemoveQueueItemAt(int index) { - if (queueEditor != null) { - queueEditor.onRemoveQueueItemAt(player, index); - } - } } } diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java index eadb320941..4f9c553a15 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java @@ -184,18 +184,13 @@ public final class TimelineQueueEditor List queue = mediaController.getQueue(); for (int i = 0; i < queue.size(); i++) { if (equalityChecker.equals(queue.get(i).getDescription(), description)) { - onRemoveQueueItemAt(player, i); + queueDataAdapter.remove(i); + queueMediaSource.removeMediaSource(i); return; } } } - @Override - public void onRemoveQueueItemAt(Player player, int index) { - queueDataAdapter.remove(index); - queueMediaSource.removeMediaSource(index); - } - // CommandReceiver implementation. @NonNull From bd8a956d53b4fa0c12954748c31939f7764f85fe Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 17 Aug 2018 09:49:03 -0700 Subject: [PATCH 02/42] Use lamdas everywhere ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209162373 --- .../exoplayer2/castdemo/MainActivity.java | 12 +- .../exoplayer2/demo/DownloadTracker.java | 13 +- .../ext/cronet/CronetDataSourceTest.java | 171 +++++++----------- .../ext/flac/FlacExtractorTest.java | 20 +- .../exoplayer2/ExoPlayerImplInternal.java | 15 +- .../audio/AudioRendererEventListener.java | 52 ++---- .../exoplayer2/drm/FrameworkMediaDrm.java | 33 ++-- .../extractor/GaplessInfoHolder.java | 15 +- .../exoplayer2/metadata/id3/Id3Decoder.java | 8 +- .../exoplayer2/offline/DownloadHelper.java | 16 +- .../exoplayer2/offline/DownloadManager.java | 135 ++++++-------- .../source/CompositeMediaSource.java | 8 +- .../exoplayer2/source/ads/AdsMediaSource.java | 80 +++----- .../exoplayer2/upstream/DummyDataSource.java | 7 +- .../exoplayer2/upstream/HttpDataSource.java | 23 +-- .../exoplayer2/upstream/cache/CacheUtil.java | 8 +- .../exoplayer2/util/SlidingPercentile.java | 16 +- .../google/android/exoplayer2/util/Util.java | 9 +- .../video/VideoRendererEventListener.java | 62 ++----- .../android/exoplayer2/ExoPlayerTest.java | 106 ++--------- .../analytics/AnalyticsCollectorTest.java | 38 +--- .../exoplayer2/extractor/Id3PeekerTest.java | 9 +- .../extractor/amr/AmrExtractorTest.java | 13 +- .../extractor/flv/FlvExtractorTest.java | 11 +- .../extractor/mkv/MatroskaExtractorTest.java | 28 +-- .../extractor/mp3/Mp3ExtractorTest.java | 20 +- .../mp4/FragmentedMp4ExtractorTest.java | 8 +- .../extractor/mp4/Mp4ExtractorTest.java | 11 +- .../extractor/ogg/OggExtractorTest.java | 9 +- .../extractor/rawcc/RawCcExtractorTest.java | 12 +- .../extractor/ts/Ac3ExtractorTest.java | 11 +- .../extractor/ts/AdtsExtractorTest.java | 21 +-- .../extractor/ts/PsExtractorTest.java | 11 +- .../extractor/ts/TsExtractorTest.java | 20 +- .../extractor/wav/WavExtractorTest.java | 11 +- .../offline/DownloadManagerTest.java | 77 ++------ .../source/ClippingMediaSourceTest.java | 8 +- .../source/ConcatenatingMediaSourceTest.java | 92 ++-------- .../upstream/cache/CacheDataSourceTest.java | 24 +-- .../upstream/cache/CacheUtilTest.java | 25 +-- .../upstream/cache/SimpleCacheTest.java | 14 +- .../source/dash/DashMediaSource.java | 14 +- .../dash/offline/DownloadManagerDashTest.java | 84 +++------ .../dash/offline/DownloadServiceDashTest.java | 103 +++++------ .../source/smoothstreaming/SsMediaSource.java | 7 +- .../exoplayer2/ui/PlayerControlView.java | 20 +- .../ui/PlayerNotificationManager.java | 13 +- .../exoplayer2/ui/TrackSelectionView.java | 9 +- .../android/exoplayer2/testutil/Action.java | 25 +-- .../exoplayer2/testutil/ActionSchedule.java | 8 +- .../exoplayer2/testutil/DummyMainThread.java | 9 +- .../exoplayer2/testutil/ExoHostedTest.java | 7 +- .../testutil/ExoPlayerTestRunner.java | 98 +++++----- .../exoplayer2/testutil/FakeMediaPeriod.java | 8 +- .../exoplayer2/testutil/FakeMediaSource.java | 15 +- .../exoplayer2/testutil/HostActivity.java | 19 +- .../exoplayer2/testutil/FakeClockTest.java | 8 +- .../exoplayer2/testutil/FakeDataSetTest.java | 7 +- .../testutil/MediaSourceTestRunner.java | 101 ++++------- .../testutil/TestDownloadManagerListener.java | 9 +- 60 files changed, 541 insertions(+), 1305 deletions(-) diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index 3e48ab2ab4..30968b8f85 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -30,8 +30,6 @@ import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; @@ -145,13 +143,9 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, ListView sampleList = dialogList.findViewById(R.id.sample_list); sampleList.setAdapter(new SampleListAdapter(this)); sampleList.setOnItemClickListener( - new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - playerManager.addItem(DemoUtil.SAMPLES.get(position)); - mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1); - } + (parent, view, position, id) -> { + playerManager.addItem(DemoUtil.SAMPLES.get(position)); + mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1); }); return dialogList; } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java index f20e41d8f7..be2dec71d5 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -175,14 +175,11 @@ public class DownloadTracker implements DownloadManager.Listener { } final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]); actionFileWriteHandler.post( - new Runnable() { - @Override - public void run() { - try { - actionFile.store(actions); - } catch (IOException e) { - Log.e(TAG, "Failed to store tracked actions", e); - } + () -> { + try { + actionFile.store(actions); + } catch (IOException e) { + Log.e(TAG, "Failed to store tracked actions", e); } }); } diff --git a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index 3e2242826c..4013caf09c 100644 --- a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -60,8 +60,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowSystemClock; @@ -170,16 +168,13 @@ public final class CronetDataSourceTest { final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2); doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - // Invoke the callback for the previous request. - dataSourceUnderTest.urlRequestCallback.onFailed( - mockUrlRequest, testUrlResponseInfo, mockNetworkException); - dataSourceUnderTest.urlRequestCallback.onResponseStarted( - mockUrlRequest2, testUrlResponseInfo); - return null; - } + invocation -> { + // Invoke the callback for the previous request. + dataSourceUnderTest.urlRequestCallback.onFailed( + mockUrlRequest, testUrlResponseInfo, mockNetworkException); + dataSourceUnderTest.urlRequestCallback.onResponseStarted( + mockUrlRequest2, testUrlResponseInfo); + return null; }) .when(mockUrlRequest2) .start(); @@ -900,14 +895,11 @@ public final class CronetDataSourceTest { private void mockStatusResponse() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - UrlRequest.StatusListener statusListener = - (UrlRequest.StatusListener) invocation.getArguments()[0]; - statusListener.onStatus(TEST_CONNECTION_STATUS); - return null; - } + invocation -> { + UrlRequest.StatusListener statusListener = + (UrlRequest.StatusListener) invocation.getArguments()[0]; + statusListener.onStatus(TEST_CONNECTION_STATUS); + return null; }) .when(mockUrlRequest) .getStatus(any(UrlRequest.StatusListener.class)); @@ -915,13 +907,10 @@ public final class CronetDataSourceTest { private void mockResponseStartSuccess() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.urlRequestCallback.onResponseStarted( - mockUrlRequest, testUrlResponseInfo); - return null; - } + invocation -> { + dataSourceUnderTest.urlRequestCallback.onResponseStarted( + mockUrlRequest, testUrlResponseInfo); + return null; }) .when(mockUrlRequest) .start(); @@ -929,15 +918,12 @@ public final class CronetDataSourceTest { private void mockResponseStartRedirect() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.urlRequestCallback.onRedirectReceived( - mockUrlRequest, - createUrlResponseInfo(307), // statusCode - "http://redirect.location.com"); - return null; - } + invocation -> { + dataSourceUnderTest.urlRequestCallback.onRedirectReceived( + mockUrlRequest, + createUrlResponseInfo(307), // statusCode + "http://redirect.location.com"); + return null; }) .when(mockUrlRequest) .start(); @@ -945,21 +931,18 @@ public final class CronetDataSourceTest { private void mockSingleRedirectSuccess() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - if (!redirectCalled) { - redirectCalled = true; - dataSourceUnderTest.urlRequestCallback.onRedirectReceived( - mockUrlRequest, - createUrlResponseInfoWithUrl("http://example.com/video", 300), - "http://example.com/video/redirect"); - } else { - dataSourceUnderTest.urlRequestCallback.onResponseStarted( - mockUrlRequest, testUrlResponseInfo); - } - return null; + invocation -> { + if (!redirectCalled) { + redirectCalled = true; + dataSourceUnderTest.urlRequestCallback.onRedirectReceived( + mockUrlRequest, + createUrlResponseInfoWithUrl("http://example.com/video", 300), + "http://example.com/video/redirect"); + } else { + dataSourceUnderTest.urlRequestCallback.onResponseStarted( + mockUrlRequest, testUrlResponseInfo); } + return null; }) .when(mockUrlRequest) .start(); @@ -967,13 +950,10 @@ public final class CronetDataSourceTest { private void mockFollowRedirectSuccess() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.urlRequestCallback.onResponseStarted( - mockUrlRequest, testUrlResponseInfo); - return null; - } + invocation -> { + dataSourceUnderTest.urlRequestCallback.onResponseStarted( + mockUrlRequest, testUrlResponseInfo); + return null; }) .when(mockUrlRequest) .followRedirect(); @@ -981,15 +961,12 @@ public final class CronetDataSourceTest { private void mockResponseStartFailure() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.urlRequestCallback.onFailed( - mockUrlRequest, - createUrlResponseInfo(500), // statusCode - mockNetworkException); - return null; - } + invocation -> { + dataSourceUnderTest.urlRequestCallback.onFailed( + mockUrlRequest, + createUrlResponseInfo(500), // statusCode + mockNetworkException); + return null; }) .when(mockUrlRequest) .start(); @@ -998,23 +975,20 @@ public final class CronetDataSourceTest { private void mockReadSuccess(int position, int length) { final int[] positionAndRemaining = new int[] {position, length}; doAnswer( - new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - if (positionAndRemaining[1] == 0) { - dataSourceUnderTest.urlRequestCallback.onSucceeded( - mockUrlRequest, testUrlResponseInfo); - } else { - ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; - int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); - inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); - positionAndRemaining[0] += readLength; - positionAndRemaining[1] -= readLength; - dataSourceUnderTest.urlRequestCallback.onReadCompleted( - mockUrlRequest, testUrlResponseInfo, inputBuffer); - } - return null; + invocation -> { + if (positionAndRemaining[1] == 0) { + dataSourceUnderTest.urlRequestCallback.onSucceeded( + mockUrlRequest, testUrlResponseInfo); + } else { + ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; + int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); + inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); + positionAndRemaining[0] += readLength; + positionAndRemaining[1] -= readLength; + dataSourceUnderTest.urlRequestCallback.onReadCompleted( + mockUrlRequest, testUrlResponseInfo, inputBuffer); } + return null; }) .when(mockUrlRequest) .read(any(ByteBuffer.class)); @@ -1022,15 +996,12 @@ public final class CronetDataSourceTest { private void mockReadFailure() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.urlRequestCallback.onFailed( - mockUrlRequest, - createUrlResponseInfo(500), // statusCode - mockNetworkException); - return null; - } + invocation -> { + dataSourceUnderTest.urlRequestCallback.onFailed( + mockUrlRequest, + createUrlResponseInfo(500), // statusCode + mockNetworkException); + return null; }) .when(mockUrlRequest) .read(any(ByteBuffer.class)); @@ -1039,12 +1010,9 @@ public final class CronetDataSourceTest { private ConditionVariable buildReadStartedCondition() { final ConditionVariable startedCondition = new ConditionVariable(); doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - startedCondition.open(); - return null; - } + invocation -> { + startedCondition.open(); + return null; }) .when(mockUrlRequest) .read(any(ByteBuffer.class)); @@ -1054,12 +1022,9 @@ public final class CronetDataSourceTest { private ConditionVariable buildUrlRequestStartedCondition() { final ConditionVariable startedCondition = new ConditionVariable(); doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - startedCondition.open(); - return null; - } + invocation -> { + startedCondition.open(); + return null; }) .when(mockUrlRequest) .start(); diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java index fc9bdac2ea..29a597daa4 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java @@ -16,9 +16,7 @@ package com.google.android.exoplayer2.ext.flac; import android.test.InstrumentationTestCase; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; /** * Unit test for {@link FlacExtractor}. @@ -35,25 +33,11 @@ public class FlacExtractorTest extends InstrumentationTestCase { public void testExtractFlacSample() throws Exception { ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new FlacExtractor(); - } - }, - "bear.flac", - getInstrumentation().getContext()); + FlacExtractor::new, "bear.flac", getInstrumentation().getContext()); } public void testExtractFlacSampleWithId3Header() throws Exception { ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new FlacExtractor(); - } - }, - "bear_with_id3.flac", - getInstrumentation().getContext()); + FlacExtractor::new, "bear_with_id3.flac", getInstrumentation().getContext()); } } 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 7e54726daf..0a5b4b72d4 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 @@ -853,15 +853,12 @@ import java.util.Collections; private void sendMessageToTargetThread(final PlayerMessage message) { Handler handler = message.getHandler(); handler.post( - new Runnable() { - @Override - public void run() { - try { - deliverMessage(message); - } catch (ExoPlaybackException e) { - Log.e(TAG, "Unexpected error delivering message on external thread.", e); - throw new RuntimeException(e); - } + () -> { + try { + deliverMessage(message); + } catch (ExoPlaybackException e) { + Log.e(TAG, "Unexpected error delivering message on external thread.", e); + throw new RuntimeException(e); } }); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 7a4958a61a..7c3c1481fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -104,12 +104,7 @@ public interface AudioRendererEventListener { */ public void enabled(final DecoderCounters decoderCounters) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onAudioEnabled(decoderCounters); - } - }); + handler.post(() -> listener.onAudioEnabled(decoderCounters)); } } @@ -119,13 +114,10 @@ public interface AudioRendererEventListener { public void decoderInitialized(final String decoderName, final long initializedTimestampMs, final long initializationDurationMs) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, - initializationDurationMs); - } - }); + handler.post( + () -> + listener.onAudioDecoderInitialized( + decoderName, initializedTimestampMs, initializationDurationMs)); } } @@ -134,12 +126,7 @@ public interface AudioRendererEventListener { */ public void inputFormatChanged(final Format format) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onAudioInputFormatChanged(format); - } - }); + handler.post(() -> listener.onAudioInputFormatChanged(format)); } } @@ -149,12 +136,8 @@ public interface AudioRendererEventListener { public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs, final long elapsedSinceLastFeedMs) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onAudioSinkUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); - } - }); + handler.post( + () -> listener.onAudioSinkUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs)); } } @@ -163,13 +146,11 @@ public interface AudioRendererEventListener { */ public void disabled(final DecoderCounters counters) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - counters.ensureUpdated(); - listener.onAudioDisabled(counters); - } - }); + handler.post( + () -> { + counters.ensureUpdated(); + listener.onAudioDisabled(counters); + }); } } @@ -178,12 +159,7 @@ public interface AudioRendererEventListener { */ public void audioSessionId(final int audioSessionId) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onAudioSessionId(audioSessionId); - } - }); + handler.post(() -> listener.onAudioSessionId(audioSessionId)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 8937768ff4..44a31a1896 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -24,8 +24,6 @@ import android.media.MediaDrm; import android.media.MediaDrmException; import android.media.NotProvisionedException; import android.media.UnsupportedSchemeException; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer2.util.Assertions; @@ -80,13 +78,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm listener) { - mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() { - @Override - public void onEvent(@NonNull MediaDrm md, @Nullable byte[] sessionId, int event, int extra, - byte[] data) { - listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data); - } - }); + mediaDrm.setOnEventListener( + listener == null + ? null + : (mediaDrm, sessionId, event, extra, data) -> + listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data)); } @Override @@ -99,20 +95,13 @@ public final class FrameworkMediaDrm implements ExoMediaDrm keyInfo, - boolean hasNewUsableKey) { - List exoKeyInfo = new ArrayList<>(); - for (MediaDrm.KeyStatus keyStatus : keyInfo) { - exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId())); - } - listener.onKeyStatusChange( - FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey); + : (mediaDrm, sessionId, keyInfo, hasNewUsableKey) -> { + List exoKeyInfo = new ArrayList<>(); + for (MediaDrm.KeyStatus keyStatus : keyInfo) { + exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId())); } + listener.onKeyStatusChange( + FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey); }, null); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java index 54d48350fc..0742d96a06 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java @@ -29,16 +29,13 @@ import java.util.regex.Pattern; public final class GaplessInfoHolder { /** - * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed - * to {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback - * information are decoded. + * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed to + * {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback information + * are decoded. */ - public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = new FramePredicate() { - @Override - public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) { - return id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2); - } - }; + public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = + (majorVersion, id0, id1, id2, id3) -> + id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2); private static final String GAPLESS_DOMAIN = "com.apple.iTunes"; private static final String GAPLESS_DESCRIPTION = "iTunSMPB"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 914fca5eef..50a443aff3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -56,13 +56,7 @@ public final class Id3Decoder implements MetadataDecoder { /** A predicate that indicates no frames should be decoded. */ public static final FramePredicate NO_FRAMES_PREDICATE = - new FramePredicate() { - - @Override - public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) { - return false; - } - }; + (majorVersion, id0, id1, id2, id3) -> false; private static final String TAG = "Id3Decoder"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index f6157c1dc3..bb82df7a48 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -59,21 +59,9 @@ public abstract class DownloadHelper { public void run() { try { prepareInternal(); - handler.post( - new Runnable() { - @Override - public void run() { - callback.onPrepared(DownloadHelper.this); - } - }); + handler.post(() -> callback.onPrepared(DownloadHelper.this)); } catch (final IOException e) { - handler.post( - new Runnable() { - @Override - public void run() { - callback.onPrepareError(DownloadHelper.this, e); - } - }); + handler.post(() -> callback.onPrepareError(DownloadHelper.this, e)); } } }.start(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 3b825bb14a..36ac04505d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -336,12 +336,7 @@ public final class DownloadManager { tasks.get(i).stop(); } final ConditionVariable fileIOFinishedCondition = new ConditionVariable(); - fileIOHandler.post(new Runnable() { - @Override - public void run() { - fileIOFinishedCondition.open(); - } - }); + fileIOHandler.post(fileIOFinishedCondition::open); fileIOFinishedCondition.block(); fileIOThread.quit(); logd("Released"); @@ -451,51 +446,45 @@ public final class DownloadManager { private void loadActions() { fileIOHandler.post( - new Runnable() { - @Override - public void run() { - DownloadAction[] loadedActions; - try { - loadedActions = actionFile.load(DownloadManager.this.deserializers); - logd("Action file is loaded."); - } catch (Throwable e) { - Log.e(TAG, "Action file loading failed.", e); - loadedActions = new DownloadAction[0]; - } - final DownloadAction[] actions = loadedActions; - handler.post( - new Runnable() { - @Override - public void run() { - if (released) { - return; - } - List pendingTasks = new ArrayList<>(tasks); - tasks.clear(); - for (DownloadAction action : actions) { - addTaskForAction(action); - } - logd("Tasks are created."); - initialized = true; - for (Listener listener : listeners) { - listener.onInitialized(DownloadManager.this); - } - if (!pendingTasks.isEmpty()) { - tasks.addAll(pendingTasks); - saveActions(); - } - maybeStartTasks(); - for (int i = 0; i < tasks.size(); i++) { - Task task = tasks.get(i); - if (task.currentState == STATE_QUEUED) { - // Task did not change out of its initial state, and so its initial state - // won't have been reported to listeners. Do so now. - notifyListenersTaskStateChange(task); - } - } - } - }); + () -> { + DownloadAction[] loadedActions; + try { + loadedActions = actionFile.load(DownloadManager.this.deserializers); + logd("Action file is loaded."); + } catch (Throwable e) { + Log.e(TAG, "Action file loading failed.", e); + loadedActions = new DownloadAction[0]; } + final DownloadAction[] actions = loadedActions; + handler.post( + () -> { + if (released) { + return; + } + List pendingTasks = new ArrayList<>(tasks); + tasks.clear(); + for (DownloadAction action : actions) { + addTaskForAction(action); + } + logd("Tasks are created."); + initialized = true; + for (Listener listener : listeners) { + listener.onInitialized(DownloadManager.this); + } + if (!pendingTasks.isEmpty()) { + tasks.addAll(pendingTasks); + saveActions(); + } + maybeStartTasks(); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.currentState == STATE_QUEUED) { + // Task did not change out of its initial state, and so its initial state + // won't have been reported to listeners. Do so now. + notifyListenersTaskStateChange(task); + } + } + }); }); } @@ -507,17 +496,15 @@ public final class DownloadManager { for (int i = 0; i < tasks.size(); i++) { actions[i] = tasks.get(i).action; } - fileIOHandler.post(new Runnable() { - @Override - public void run() { - try { - actionFile.store(actions); - logd("Actions persisted."); - } catch (IOException e) { - Log.e(TAG, "Persisting actions failed.", e); - } - } - }); + fileIOHandler.post( + () -> { + try { + actionFile.store(actions); + logd("Actions persisted."); + } catch (IOException e) { + Log.e(TAG, "Persisting actions failed.", e); + } + }); } private static void logd(String message) { @@ -771,12 +758,7 @@ public final class DownloadManager { private void cancel() { if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) { downloadManager.handler.post( - new Runnable() { - @Override - public void run() { - changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED); - } - }); + () -> changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED)); } else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) { cancelDownload(); } @@ -851,19 +833,14 @@ public final class DownloadManager { } final Throwable finalError = error; downloadManager.handler.post( - new Runnable() { - @Override - public void run() { - if (changeStateAndNotify( - STATE_STARTED, - finalError != null ? STATE_FAILED : STATE_COMPLETED, - finalError) - || changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED) - || changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) { - return; - } - throw new IllegalStateException(); + () -> { + if (changeStateAndNotify( + STATE_STARTED, finalError != null ? STATE_FAILED : STATE_COMPLETED, finalError) + || changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED) + || changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) { + return; } + throw new IllegalStateException(); }); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java index 2ef5186224..69fa4b094b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java @@ -101,13 +101,7 @@ public abstract class CompositeMediaSource extends BaseMediaSource { protected final void prepareChildSource(final T id, MediaSource mediaSource) { Assertions.checkArgument(!childSources.containsKey(id)); SourceInfoRefreshListener sourceListener = - new SourceInfoRefreshListener() { - @Override - public void onSourceInfoRefreshed( - MediaSource source, Timeline timeline, @Nullable Object manifest) { - onChildSourceInfoRefreshed(id, source, timeline, manifest); - } - }; + (source, timeline, manifest) -> onChildSourceInfoRefreshed(id, source, timeline, manifest); MediaSourceEventListener eventListener = new ForwardingEventListener(id); childSources.put(id, new MediaSourceAndListener(mediaSource, sourceListener, eventListener)); mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 66370828b7..e3df34bed8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -325,12 +325,7 @@ public final class AdsMediaSource extends CompositeMediaSource { final ComponentListener componentListener = new ComponentListener(); this.componentListener = componentListener; prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource); - mainHandler.post(new Runnable() { - @Override - public void run() { - adsLoader.attachPlayer(player, componentListener, adUiViewGroup); - } - }); + mainHandler.post(() -> adsLoader.attachPlayer(player, componentListener, adUiViewGroup)); } @Override @@ -397,12 +392,7 @@ public final class AdsMediaSource extends CompositeMediaSource { adPlaybackState = null; adGroupMediaSources = new MediaSource[0][]; adDurationsUs = new long[0][]; - mainHandler.post(new Runnable() { - @Override - public void run() { - adsLoader.detachPlayer(); - } - }); + mainHandler.post(adsLoader::detachPlayer); } @Override @@ -500,15 +490,13 @@ public final class AdsMediaSource extends CompositeMediaSource { if (released) { return; } - playerHandler.post(new Runnable() { - @Override - public void run() { - if (released) { - return; - } - AdsMediaSource.this.onAdPlaybackState(adPlaybackState); - } - }); + playerHandler.post( + () -> { + if (released) { + return; + } + AdsMediaSource.this.onAdPlaybackState(adPlaybackState); + }); } @Override @@ -517,14 +505,12 @@ public final class AdsMediaSource extends CompositeMediaSource { return; } if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - if (!released) { - eventListener.onAdClicked(); - } - } - }); + eventHandler.post( + () -> { + if (!released) { + eventListener.onAdClicked(); + } + }); } } @@ -534,14 +520,12 @@ public final class AdsMediaSource extends CompositeMediaSource { return; } if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - if (!released) { - eventListener.onAdTapped(); - } - } - }); + eventHandler.post( + () -> { + if (!released) { + eventListener.onAdTapped(); + } + }); } } @@ -562,15 +546,12 @@ public final class AdsMediaSource extends CompositeMediaSource { /* wasCanceled= */ true); if (eventHandler != null && eventListener != null) { eventHandler.post( - new Runnable() { - @Override - public void run() { - if (!released) { - if (error.type == AdLoadException.TYPE_UNEXPECTED) { - eventListener.onInternalAdLoadError(error.getRuntimeExceptionForUnexpected()); - } else { - eventListener.onAdLoadError(error); - } + () -> { + if (!released) { + if (error.type == AdLoadException.TYPE_UNEXPECTED) { + eventListener.onInternalAdLoadError(error.getRuntimeExceptionForUnexpected()); + } else { + eventListener.onAdLoadError(error); } } }); @@ -603,12 +584,7 @@ public final class AdsMediaSource extends CompositeMediaSource { AdLoadException.createForAd(exception), /* wasCanceled= */ true); mainHandler.post( - new Runnable() { - @Override - public void run() { - adsLoader.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception); - } - }); + () -> adsLoader.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception)); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java index 06dc79e345..13c5732a62 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java @@ -27,12 +27,7 @@ public final class DummyDataSource implements DataSource { public static final DummyDataSource INSTANCE = new DummyDataSource(); /** A factory that produces {@link DummyDataSource}. */ - public static final Factory FACTORY = new Factory() { - @Override - public DataSource createDataSource() { - return new DummyDataSource(); - } - }; + public static final Factory FACTORY = DummyDataSource::new; private DummyDataSource() {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 71a0e68260..daf5d3281a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -211,20 +211,15 @@ public interface HttpDataSource extends DataSource { } - /** - * A {@link Predicate} that rejects content types often used for pay-walls. - */ - Predicate REJECT_PAYWALL_TYPES = new Predicate() { - - @Override - public boolean evaluate(String contentType) { - contentType = Util.toLowerInvariant(contentType); - return !TextUtils.isEmpty(contentType) - && (!contentType.contains("text") || contentType.contains("text/vtt")) - && !contentType.contains("html") && !contentType.contains("xml"); - } - - }; + /** A {@link Predicate} that rejects content types often used for pay-walls. */ + Predicate REJECT_PAYWALL_TYPES = + contentType -> { + contentType = Util.toLowerInvariant(contentType); + return !TextUtils.isEmpty(contentType) + && (!contentType.contains("text") || contentType.contains("text/vtt")) + && !contentType.contains("html") + && !contentType.contains("xml"); + }; /** * Thrown when an error is encountered when trying to read from a {@link HttpDataSource}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java index 1bdaa8e3fa..cee22375a9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -55,13 +55,7 @@ public final class CacheUtil { public static final int DEFAULT_BUFFER_SIZE_BYTES = 128 * 1024; /** Default {@link CacheKeyFactory} that calls through to {@link #getKey}. */ - public static final CacheKeyFactory DEFAULT_CACHE_KEY_FACTORY = - new CacheKeyFactory() { - @Override - public String buildCacheKey(DataSpec dataSpec) { - return getKey(dataSpec); - } - }; + public static final CacheKeyFactory DEFAULT_CACHE_KEY_FACTORY = CacheUtil::getKey; /** * Generates a cache key out of the given {@link Uri}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java index c43b1929cb..f9be1a53b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java @@ -35,19 +35,9 @@ import java.util.Comparator; public class SlidingPercentile { // Orderings. - private static final Comparator INDEX_COMPARATOR = new Comparator() { - @Override - public int compare(Sample a, Sample b) { - return a.index - b.index; - } - }; - - private static final Comparator VALUE_COMPARATOR = new Comparator() { - @Override - public int compare(Sample a, Sample b) { - return a.value < b.value ? -1 : b.value < a.value ? 1 : 0; - } - }; + private static final Comparator INDEX_COMPARATOR = (a, b) -> a.index - b.index; + private static final Comparator VALUE_COMPARATOR = + (a, b) -> Float.compare(a.value, b.value); private static final int SORT_ORDER_NONE = -1; private static final int SORT_ORDER_BY_VALUE = 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 58a4f64816..160dccbcad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -34,7 +34,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.security.NetworkSecurityPolicy; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -67,7 +66,6 @@ import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.DataFormatException; @@ -341,12 +339,7 @@ public final class Util { * @return The executor. */ public static ExecutorService newSingleThreadExecutor(final String threadName) { - return Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(@NonNull Runnable r) { - return new Thread(r, threadName); - } - }); + return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, threadName)); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index d6ea0ebae2..f96aae77b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -129,12 +129,7 @@ public interface VideoRendererEventListener { */ public void enabled(final DecoderCounters decoderCounters) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onVideoEnabled(decoderCounters); - } - }); + handler.post(() -> listener.onVideoEnabled(decoderCounters)); } } @@ -144,13 +139,10 @@ public interface VideoRendererEventListener { public void decoderInitialized(final String decoderName, final long initializedTimestampMs, final long initializationDurationMs) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, - initializationDurationMs); - } - }); + handler.post( + () -> + listener.onVideoDecoderInitialized( + decoderName, initializedTimestampMs, initializationDurationMs)); } } @@ -159,12 +151,7 @@ public interface VideoRendererEventListener { */ public void inputFormatChanged(final Format format) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onVideoInputFormatChanged(format); - } - }); + handler.post(() -> listener.onVideoInputFormatChanged(format)); } } @@ -173,12 +160,7 @@ public interface VideoRendererEventListener { */ public void droppedFrames(final int droppedFrameCount, final long elapsedMs) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onDroppedFrames(droppedFrameCount, elapsedMs); - } - }); + handler.post(() -> listener.onDroppedFrames(droppedFrameCount, elapsedMs)); } } @@ -188,13 +170,10 @@ public interface VideoRendererEventListener { public void videoSizeChanged(final int width, final int height, final int unappliedRotationDegrees, final float pixelWidthHeightRatio) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, - pixelWidthHeightRatio); - } - }); + handler.post( + () -> + listener.onVideoSizeChanged( + width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); } } @@ -203,12 +182,7 @@ public interface VideoRendererEventListener { */ public void renderedFirstFrame(final Surface surface) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onRenderedFirstFrame(surface); - } - }); + handler.post(() -> listener.onRenderedFirstFrame(surface)); } } @@ -217,13 +191,11 @@ public interface VideoRendererEventListener { */ public void disabled(final DecoderCounters counters) { if (listener != null) { - handler.post(new Runnable() { - @Override - public void run() { - counters.ensureUpdated(); - listener.onVideoDisabled(counters); - } - }); + handler.post( + () -> { + counters.ensureUpdated(); + listener.onVideoDisabled(counters); + }); } } 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 1e676f2123..5cba9267b5 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 @@ -277,24 +277,15 @@ public final class ExoPlayerTest { .waitForTimelineChanged(timeline) .prepareSource(secondSource) .executeRunnable( - new Runnable() { - @Override - public void run() { - try { - queuedSourceInfoCountDownLatch.await(); - } catch (InterruptedException e) { - // Ignore. - } + () -> { + try { + queuedSourceInfoCountDownLatch.await(); + } catch (InterruptedException e) { + // Ignore. } }) .prepareSource(thirdSource) - .executeRunnable( - new Runnable() { - @Override - public void run() { - completePreparationCountDownLatch.countDown(); - } - }) + .executeRunnable(completePreparationCountDownLatch::countDown) .build(); ExoPlayerTestRunner testRunner = new Builder() @@ -436,13 +427,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder("testAdGroupWithLoadErrorIsSkipped") .pause() .waitForPlaybackState(Player.STATE_READY) - .executeRunnable( - new Runnable() { - @Override - public void run() { - fakeMediaSource.setNewSourceInfo(adErrorTimeline, null); - } - }) + .executeRunnable(() -> fakeMediaSource.setNewSourceInfo(adErrorTimeline, null)) .waitForTimelineChanged(adErrorTimeline) .play() .build(); @@ -823,13 +808,7 @@ public final class ExoPlayerTest { new ActionSchedule.Builder("testDynamicTimelineChangeReason") .pause() .waitForTimelineChanged(timeline1) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(timeline2, null); - } - }) + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2, null)) .waitForTimelineChanged(timeline2) .play() .build(); @@ -911,26 +890,17 @@ public final class ExoPlayerTest { .waitForPlaybackState(Player.STATE_BUFFERING) // Block until createPeriod has been called on the fake media source. .executeRunnable( - new Runnable() { - @Override - public void run() { - try { - createPeriodCalledCountDownLatch.await(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } + () -> { + try { + createPeriodCalledCountDownLatch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); } }) // Set playback parameters (while the fake media period is not yet prepared). .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f, /* pitch= */ 2f)) // Complete preparation of the fake media period. - .executeRunnable( - new Runnable() { - @Override - public void run() { - fakeMediaPeriodHolder[0].setPreparationComplete(); - } - }) + .executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete()) .build(); new ExoPlayerTestRunner.Builder() .setMediaSource(mediaSource) @@ -1280,13 +1250,7 @@ public final class ExoPlayerTest { // is still being prepared. The error will be thrown while the player handles the new // source info. .seek(/* windowIndex= */ 100, /* positionMs= */ 0) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null); - } - }) + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) .waitForPlaybackState(Player.STATE_IDLE) .build(); ExoPlayerTestRunner testRunner = @@ -1789,13 +1753,7 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged(timeline) .sendMessage(target, /* positionMs= */ 50) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(secondTimeline, null); - } - }) + .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline, null)) .waitForTimelineChanged(secondTimeline) .play() .build(); @@ -1868,13 +1826,7 @@ public final class ExoPlayerTest { .pause() .waitForTimelineChanged(timeline) .sendMessage(target, /* windowIndex = */ 1, /* positionMs= */ 50) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(secondTimeline, null); - } - }) + .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline, null)) .waitForTimelineChanged(secondTimeline) .seek(/* windowIndex= */ 0, /* positionMs= */ 0) .play() @@ -1943,13 +1895,7 @@ public final class ExoPlayerTest { }) // Play a bit to ensure message arrived in internal player. .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 30) - .executeRunnable( - new Runnable() { - @Override - public void run() { - message.get().cancel(); - } - }) + .executeRunnable(() -> message.get().cancel()) .play() .build(); new Builder() @@ -1987,13 +1933,7 @@ public final class ExoPlayerTest { .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 51) // Seek back, cancel the message, and play past the same position again. .seek(/* positionMs= */ 0) - .executeRunnable( - new Runnable() { - @Override - public void run() { - message.get().cancel(); - } - }) + .executeRunnable(() -> message.get().cancel()) .play() .build(); new Builder() @@ -2064,13 +2004,7 @@ public final class ExoPlayerTest { .playUntilPosition( /* windowIndex= */ 0, /* positionMs= */ C.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(timeline2, /* newManifest= */ null); - } - }) + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2, /* newManifest= */ null)) .waitForTimelineChanged(timeline2) .play() .build(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index 3216087169..8a2612cacc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -17,9 +17,6 @@ package com.google.android.exoplayer2.analytics; import static com.google.common.truth.Truth.assertThat; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.os.Handler; import android.os.SystemClock; import android.support.annotation.Nullable; @@ -35,10 +32,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; @@ -53,7 +47,6 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeRenderer; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.RobolectricUtil; -import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -601,13 +594,9 @@ public final class AnalyticsCollectorTest { // Ensure second period is already being read from. .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ periodDurationMs) .executeRunnable( - new Runnable() { - @Override - public void run() { + () -> concatenatedMediaSource.moveMediaSource( - /* currentIndex= */ 0, /* newIndex= */ 1); - } - }) + /* currentIndex= */ 0, /* newIndex= */ 1)) .waitForTimelineChanged(/* expectedTimeline= */ null) .play() .build(); @@ -658,10 +647,6 @@ public final class AnalyticsCollectorTest { @Test public void testNotifyExternalEvents() throws Exception { MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE, /* manifest= */ null); - final NetworkInfo networkInfo = - ((ConnectivityManager) - RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE)) - .getActiveNetworkInfo(); ActionSchedule actionSchedule = new ActionSchedule.Builder("AnalyticsCollectorTest") .pause() @@ -689,21 +674,16 @@ public final class AnalyticsCollectorTest { private static TestAnalyticsListener runAnalyticsTest( MediaSource mediaSource, @Nullable ActionSchedule actionSchedule) throws Exception { RenderersFactory renderersFactory = - new RenderersFactory() { - @Override - public Renderer[] createRenderers( - Handler eventHandler, - VideoRendererEventListener videoRendererEventListener, - AudioRendererEventListener audioRendererEventListener, - TextOutput textRendererOutput, - MetadataOutput metadataRendererOutput, - @Nullable DrmSessionManager drmSessionManager) { - return new Renderer[] { + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput, + drmSessionManager) -> + new Renderer[] { new FakeVideoRenderer(eventHandler, videoRendererEventListener), new FakeAudioRenderer(eventHandler, audioRendererEventListener) }; - } - }; TestAnalyticsListener listener = new TestAnalyticsListener(); try { new ExoPlayerTestRunner.Builder() diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java index a397f70886..f43f356482 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.metadata.id3.CommentFrame; -import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3DecoderTest; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import java.io.IOException; @@ -95,12 +94,8 @@ public final class Id3PeekerTest { Metadata metadata = id3Peeker.peekId3Data( input, - new Id3Decoder.FramePredicate() { - @Override - public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) { - return id0 == 'C' && id1 == 'O' && id2 == 'M' && id3 == 'M'; - } - }); + (majorVersion, id0, id1, id2, id3) -> + id0 == 'C' && id1 == 'O' && id2 == 'M' && id3 == 'M'); assertThat(metadata.length()).isEqualTo(1); CommentFrame commentFrame = (CommentFrame) metadata.get(0); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java index 39c1bfe05b..c3c33e3350 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java @@ -250,14 +250,11 @@ public final class AmrExtractorTest { @NonNull private static ExtractorAsserts.ExtractorFactory createAmrExtractorFactory(boolean withSeeking) { - return new ExtractorAsserts.ExtractorFactory() { - @Override - public Extractor create() { - if (!withSeeking) { - return new AmrExtractor(); - } else { - return new AmrExtractor(AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING); - } + return () -> { + if (!withSeeking) { + return new AmrExtractor(); + } else { + return new AmrExtractor(AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING); } }; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java index 5a093988dd..316148d9b9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.flv; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,13 +26,6 @@ public final class FlvExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new FlvExtractor(); - } - }, - "flv/sample.flv"); + ExtractorAsserts.assertBehavior(FlvExtractor::new, "flv/sample.flv"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java index 4a0f87a80a..2e673037d0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mkv; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,37 +26,17 @@ public final class MatroskaExtractorTest { @Test public void testMkvSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, - "mkv/sample.mkv"); + ExtractorAsserts.assertBehavior(MatroskaExtractor::new, "mkv/sample.mkv"); } @Test public void testWebmSubsampleEncryption() throws Exception { ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, - "mkv/subsample_encrypted_noaltref.webm"); + MatroskaExtractor::new, "mkv/subsample_encrypted_noaltref.webm"); } @Test public void testWebmSubsampleEncryptionWithAltrefFrames() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, - "mkv/subsample_encrypted_altref.webm"); + ExtractorAsserts.assertBehavior(MatroskaExtractor::new, "mkv/subsample_encrypted_altref.webm"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java index b977766a1c..62a4f1a193 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mp3; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,25 +26,11 @@ public final class Mp3ExtractorTest { @Test public void testMp3Sample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp3Extractor(); - } - }, - "mp3/bear.mp3"); + ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/bear.mp3"); } @Test public void testTrimmedMp3Sample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp3Extractor(); - } - }, - "mp3/play-trimmed.mp3"); + ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/play-trimmed.mp3"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java index 8662434f81..f9362f9cda 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.extractor.mp4; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.util.MimeTypes; @@ -53,11 +52,6 @@ public final class FragmentedMp4ExtractorTest { } private static ExtractorFactory getExtractorFactory(final List closedCaptionFormats) { - return new ExtractorFactory() { - @Override - public Extractor create() { - return new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats); - } - }; + return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index f1812a69c4..8850a755be 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -16,9 +16,7 @@ package com.google.android.exoplayer2.extractor.mp4; import android.annotation.TargetApi; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -30,13 +28,6 @@ public final class Mp4ExtractorTest { @Test public void testMp4Sample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp4Extractor(); - } - }, - "mp4/sample.mp4"); + ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample.mp4"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java index 20808f73f2..289c168725 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.ogg; import static com.google.common.truth.Truth.assertThat; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.testutil.FakeExtractorInput; @@ -32,13 +31,7 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public final class OggExtractorTest { - private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = - new ExtractorFactory() { - @Override - public Extractor create() { - return new OggExtractor(); - } - }; + private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = OggExtractor::new; @Test public void testOpus() throws Exception { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java index 565d609842..62ad774fd3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java @@ -17,9 +17,7 @@ package com.google.android.exoplayer2.extractor.rawcc; import android.annotation.TargetApi; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.util.MimeTypes; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,10 +31,8 @@ public final class RawCcExtractorTest { @Test public void testRawCcSample() throws Exception { ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new RawCcExtractor( + () -> + new RawCcExtractor( Format.createTextContainerFormat( /* id= */ null, /* label= */ null, @@ -46,9 +42,7 @@ public final class RawCcExtractorTest { /* bitrate= */ Format.NO_VALUE, /* selectionFlags= */ 0, /* language= */ null, - /* accessibilityChannel= */ 1)); - } - }, + /* accessibilityChannel= */ 1)), "rawcc/sample.rawcc"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java index ec7afeeeab..4afd6979dc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ts; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,13 +26,6 @@ public final class Ac3ExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new Ac3Extractor(); - } - }, - "ts/sample.ac3"); + ExtractorAsserts.assertBehavior(Ac3Extractor::new, "ts/sample.ac3"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java index fe2046cbe4..7f0db67133 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ts; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,27 +26,16 @@ public final class AdtsExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new AdtsExtractor(); - } - }, - "ts/sample.adts"); + ExtractorAsserts.assertBehavior(AdtsExtractor::new, "ts/sample.adts"); } @Test public void testSample_withSeeking() throws Exception { ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new AdtsExtractor( + () -> + new AdtsExtractor( /* firstStreamSampleTimestampUs= */ 0, - /* flags= */ AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING); - } - }, + /* flags= */ AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING), "ts/sample_cbs.adts"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java index 798f1ce5e3..0e0fd52175 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ts; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,13 +26,6 @@ public final class PsExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new PsExtractor(); - } - }, - "ts/sample.ps"); + ExtractorAsserts.assertBehavior(PsExtractor::new, "ts/sample.ps"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 2f3813e9e3..332fbe384a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -27,7 +27,6 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; @@ -50,14 +49,7 @@ public final class TsExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new TsExtractor(); - } - }, - "ts/sample.ts"); + ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample.ts"); } @Test @@ -82,15 +74,7 @@ public final class TsExtractorTest { fileData = out.toByteArray(); ExtractorAsserts.assertOutput( - new ExtractorFactory() { - @Override - public Extractor create() { - return new TsExtractor(); - } - }, - "ts/sample.ts", - fileData, - RuntimeEnvironment.application); + TsExtractor::new, "ts/sample.ts", fileData, RuntimeEnvironment.application); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java index e75525bb1e..f4df4036f6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java @@ -15,9 +15,7 @@ */ package com.google.android.exoplayer2.extractor.wav; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; -import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -28,13 +26,6 @@ public final class WavExtractorTest { @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior( - new ExtractorFactory() { - @Override - public Extractor create() { - return new WavExtractor(); - } - }, - "wav/sample.wav"); + ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample.wav"); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 4a1876f69c..234377895f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -331,13 +331,7 @@ public class DownloadManagerTest { remove2Action.post().assertStarted(); download2Action.post().assertDoesNotStart(); - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.stopDownloads(); - } - }); + runOnMainThread(() -> downloadManager.stopDownloads()); download1Action.assertStopped(); @@ -354,13 +348,7 @@ public class DownloadManagerTest { // New download actions can be added but they don't start. download3Action.post().assertDoesNotStart(); - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.startDownloads(); - } - }); + runOnMainThread(() -> downloadManager.startDownloads()); download2Action.assertStarted().unblock().assertCompleted(); download3Action.assertStarted().unblock().assertCompleted(); @@ -380,24 +368,12 @@ public class DownloadManagerTest { // download3Action doesn't start as DM was configured to run two downloads in parallel. download3Action.post().assertDoesNotStart(); - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.stopDownloads(); - } - }); + runOnMainThread(() -> downloadManager.stopDownloads()); // download1Action doesn't stop yet as it ignores interrupts. download2Action.assertStopped(); - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.startDownloads(); - } - }); + runOnMainThread(() -> downloadManager.startDownloads()); // download2Action starts immediately. download2Action.assertStarted(); @@ -421,22 +397,19 @@ public class DownloadManagerTest { } try { runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager = - new DownloadManager( - new DownloaderConstructorHelper( - Mockito.mock(Cache.class), DummyDataSource.FACTORY), - maxActiveDownloadTasks, - MIN_RETRY_COUNT, - actionFile, - ProgressiveDownloadAction.DESERIALIZER); - downloadManagerListener = - new TestDownloadManagerListener(downloadManager, dummyMainThread); - downloadManager.addListener(downloadManagerListener); - downloadManager.startDownloads(); - } + () -> { + downloadManager = + new DownloadManager( + new DownloaderConstructorHelper( + Mockito.mock(Cache.class), DummyDataSource.FACTORY), + maxActiveDownloadTasks, + MIN_RETRY_COUNT, + actionFile, + ProgressiveDownloadAction.DESERIALIZER); + downloadManagerListener = + new TestDownloadManagerListener(downloadManager, dummyMainThread); + downloadManager.addListener(downloadManagerListener); + downloadManager.startDownloads(); }); } catch (Throwable throwable) { throw new Exception(throwable); @@ -445,13 +418,7 @@ public class DownloadManagerTest { private void releaseDownloadManager() throws Exception { try { - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.release(); - } - }); + runOnMainThread(() -> downloadManager.release()); } catch (Throwable throwable) { throw new Exception(throwable); } @@ -519,13 +486,7 @@ public class DownloadManagerTest { } private FakeDownloadAction post() { - runOnMainThread( - new Runnable() { - @Override - public void run() { - downloadManager.handleAction(FakeDownloadAction.this); - } - }); + runOnMainThread(() -> downloadManager.handleAction(FakeDownloadAction.this)); return this; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 0209ff86a2..1008ac26bf 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -501,9 +501,7 @@ public final class ClippingMediaSourceTest { final MediaLoadData[] reportedMediaLoadData = new MediaLoadData[1]; try { testRunner.runOnPlaybackThread( - new Runnable() { - @Override - public void run() { + () -> clippingMediaSource.addEventListener( new Handler(), new DefaultMediaSourceEventListener() { @@ -514,9 +512,7 @@ public final class ClippingMediaSourceTest { MediaLoadData mediaLoadData) { reportedMediaLoadData[0] = mediaLoadData; } - }); - } - }); + })); testRunner.prepareSource(); // Create period to send the test event configured above. testRunner.createPeriod( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index f9c327ed2b..49b4a0d24f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -247,12 +247,7 @@ public final class ConcatenatingMediaSourceTest { // Trigger source info refresh for lazy source and check that the timeline now contains all // information for all windows. testRunner.runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - lazySources[1].setNewSourceInfo(createFakeTimeline(8), null); - } - }); + () -> lazySources[1].setNewSourceInfo(createFakeTimeline(8), null)); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 9); TimelineAsserts.assertWindowTags(timeline, 111, 999); @@ -292,12 +287,7 @@ public final class ConcatenatingMediaSourceTest { // Trigger source info refresh for lazy media source. Assert that now all information is // available again and the previously created period now also finished preparing. testRunner.runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - lazySources[3].setNewSourceInfo(createFakeTimeline(7), null); - } - }); + () -> lazySources[3].setNewSourceInfo(createFakeTimeline(7), null)); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9); TimelineAsserts.assertWindowTags(timeline, 888, 111, 222, 999); @@ -484,12 +474,7 @@ public final class ConcatenatingMediaSourceTest { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber); - } - }); + () -> mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(1); } finally { @@ -504,15 +489,11 @@ public final class ConcatenatingMediaSourceTest { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { + () -> mediaSource.addMediaSources( Arrays.asList( new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), - timelineGrabber); - } - }); + timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(2); } finally { @@ -527,12 +508,8 @@ public final class ConcatenatingMediaSourceTest { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber); - } - }); + () -> + mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(1); } finally { @@ -547,16 +524,12 @@ public final class ConcatenatingMediaSourceTest { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { + () -> mediaSource.addMediaSources( /* index */ 0, Arrays.asList( new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), - timelineGrabber); - } - }); + timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(2); } finally { @@ -569,23 +542,12 @@ public final class ConcatenatingMediaSourceTest { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); - dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(createFakeMediaSource()); - } - }); + dummyMainThread.runOnMainThread(() -> mediaSource.addMediaSource(createFakeMediaSource())); testRunner.assertTimelineChangeBlocking(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.removeMediaSource(/* index */ 0, timelineGrabber); - } - }); + () -> mediaSource.removeMediaSource(/* index */ 0, timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(0); } finally { @@ -599,24 +561,15 @@ public final class ConcatenatingMediaSourceTest { try { testRunner.prepareSource(); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { + () -> mediaSource.addMediaSources( Arrays.asList( - new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()})); - } - }); + new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}))); testRunner.assertTimelineChangeBlocking(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, timelineGrabber); - } - }); + () -> mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(2); } finally { @@ -849,23 +802,14 @@ public final class ConcatenatingMediaSourceTest { final FakeMediaSource unpreparedChildSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(preparedChildSource); - mediaSource.addMediaSource(unpreparedChildSource); - } + () -> { + mediaSource.addMediaSource(preparedChildSource); + mediaSource.addMediaSource(unpreparedChildSource); }); testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); - dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - mediaSource.clear(timelineGrabber); - } - }); + dummyMainThread.runOnMainThread(() -> mediaSource.clear(timelineGrabber)); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.isEmpty()).isTrue(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 6b48cffdd5..55d05eb7d4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -60,13 +60,7 @@ public final class CacheDataSourceTest { testDataUri = Uri.parse("test_data"); fixedCacheKey = CacheUtil.generateKey(testDataUri); expectedCacheKey = fixedCacheKey; - cacheKeyFactory = - new CacheKeyFactory() { - @Override - public String buildCacheKey(DataSpec dataSpec) { - return CACHE_KEY_PREFIX + "." + CacheUtil.generateKey(dataSpec.uri); - } - }; + cacheKeyFactory = dataSpec -> CACHE_KEY_PREFIX + "." + CacheUtil.generateKey(dataSpec.uri); tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); } @@ -366,13 +360,7 @@ public final class CacheDataSourceTest { // Insert an action just before the end of the data to fail the test if reading from upstream // reaches end of the data. fakeData - .appendReadAction( - new Runnable() { - @Override - public void run() { - fail("Read from upstream shouldn't reach to the end of the data."); - } - }) + .appendReadAction(() -> fail("Read from upstream shouldn't reach to the end of the data.")) .appendReadData(1); // Create cache read-only CacheDataSource. CacheDataSource cacheDataSource = @@ -408,13 +396,7 @@ public final class CacheDataSourceTest { // Insert an action just before the end of the data to fail the test if reading from upstream // reaches end of the data. fakeData - .appendReadAction( - new Runnable() { - @Override - public void run() { - fail("Read from upstream shouldn't reach to the end of the data."); - } - }) + .appendReadAction(() -> fail("Read from upstream shouldn't reach to the end of the data.")) .appendReadData(1); // Lock the content on the cache. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java index e3917b58d0..36fb78894f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java @@ -292,22 +292,15 @@ public final class CacheUtilTest { @Test public void testCachePolling() throws Exception { final CachingCounters counters = new CachingCounters(); - FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data") - .appendReadData(TestUtil.buildTestData(100)) - .appendReadAction(new Runnable() { - @Override - public void run() { - assertCounters(counters, 0, 100, 300); - } - }) - .appendReadData(TestUtil.buildTestData(100)) - .appendReadAction(new Runnable() { - @Override - public void run() { - assertCounters(counters, 0, 200, 300); - } - }) - .appendReadData(TestUtil.buildTestData(100)).endData(); + FakeDataSet fakeDataSet = + new FakeDataSet() + .newData("test_data") + .appendReadData(TestUtil.buildTestData(100)) + .appendReadAction(() -> assertCounters(counters, 0, 100, 300)) + .appendReadData(TestUtil.buildTestData(100)) + .appendReadAction(() -> assertCounters(counters, 0, 200, 300)) + .appendReadData(TestUtil.buildTestData(100)) + .endData(); FakeDataSource dataSource = new FakeDataSource(fakeDataSet); CacheUtil.cache( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java index 15e2b80f59..a4e444386a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java @@ -37,8 +37,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -259,12 +257,12 @@ public class SimpleCacheTest { addCache(simpleCache, KEY_1, 0, 15); // Make index.store() throw exception from now on. - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - throw new Cache.CacheException("SimpleCacheTest"); - } - }).when(index).store(); + doAnswer( + invocation -> { + throw new CacheException("SimpleCacheTest"); + }) + .when(index) + .store(); // Adding more content will make LeastRecentlyUsedCacheEvictor evict previous content. try { 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 ede757aed0..ec73c3a05e 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 @@ -589,18 +589,8 @@ public final class DashMediaSource extends BaseMediaSource { } else { manifestCallback = new ManifestCallback(); manifestLoadErrorThrower = new ManifestLoadErrorThrower(); - refreshManifestRunnable = new Runnable() { - @Override - public void run() { - startLoadingManifest(); - } - }; - simulateManifestRefreshRunnable = new Runnable() { - @Override - public void run() { - processManifest(false); - } - }; + refreshManifestRunnable = this::startLoadingManifest; + simulateManifestRefreshRunnable = () -> processManifest(false); } } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java index d2ba826c66..d161c91b1d 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java @@ -110,18 +110,14 @@ public class DownloadManagerDashTest { fakeDataSet .newData(TEST_MPD_URI) .appendReadAction( - new Runnable() { - @SuppressWarnings("InfiniteLoopStatement") - @Override - public void run() { - try { - // Wait until interrupted. - while (true) { - Thread.sleep(100000); - } - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); + () -> { + try { + // Wait until interrupted. + while (true) { + Thread.sleep(100000); } + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); } }) .appendReadData(TEST_MPD) @@ -130,13 +126,10 @@ public class DownloadManagerDashTest { // Run DM accessing code on UI/main thread as it should be. Also not to block handling of loaded // actions. dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - // Setup an Action and immediately release the DM. - handleDownloadAction(fakeStreamKey1, fakeStreamKey2); - downloadManager.release(); - } + () -> { + // Setup an Action and immediately release the DM. + handleDownloadAction(fakeStreamKey1, fakeStreamKey2); + downloadManager.release(); }); assertThat(actionFile.exists()).isTrue(); @@ -146,13 +139,7 @@ public class DownloadManagerDashTest { // Revert fakeDataSet to normal. fakeDataSet.setData(TEST_MPD_URI, TEST_MPD); - dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - createDownloadManager(); - } - }); + dummyMainThread.runOnMainThread(this::createDownloadManager); // Block on the test thread. blockUntilTasksCompleteAndThrowAnyDownloadError(); @@ -178,13 +165,7 @@ public class DownloadManagerDashTest { public void testHandleInterferingDownloadAction() throws Throwable { fakeDataSet .newData("audio_segment_2") - .appendReadAction( - new Runnable() { - @Override - public void run() { - handleDownloadAction(fakeStreamKey2); - } - }) + .appendReadAction(() -> handleDownloadAction(fakeStreamKey2)) .appendReadData(TestUtil.buildTestData(5)) .endData(); @@ -224,13 +205,7 @@ public class DownloadManagerDashTest { final ConditionVariable downloadInProgressCondition = new ConditionVariable(); fakeDataSet .newData("audio_segment_2") - .appendReadAction( - new Runnable() { - @Override - public void run() { - downloadInProgressCondition.open(); - } - }) + .appendReadAction(downloadInProgressCondition::open) .appendReadData(TestUtil.buildTestData(5)) .endData(); @@ -259,24 +234,21 @@ public class DownloadManagerDashTest { private void createDownloadManager() { dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - Factory fakeDataSourceFactory = - new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); - downloadManager = - new DownloadManager( - new DownloaderConstructorHelper(cache, fakeDataSourceFactory), - /* maxSimultaneousDownloads= */ 1, - /* minRetryCount= */ 3, - actionFile, - DashDownloadAction.DESERIALIZER); + () -> { + Factory fakeDataSourceFactory = + new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); + downloadManager = + new DownloadManager( + new DownloaderConstructorHelper(cache, fakeDataSourceFactory), + /* maxSimultaneousDownloads= */ 1, + /* minRetryCount= */ 3, + actionFile, + DashDownloadAction.DESERIALIZER); - downloadManagerListener = - new TestDownloadManagerListener(downloadManager, dummyMainThread); - downloadManager.addListener(downloadManagerListener); - downloadManager.startDownloads(); - } + downloadManagerListener = + new TestDownloadManagerListener(downloadManager, dummyMainThread); + downloadManager.addListener(downloadManagerListener); + downloadManager.startDownloads(); }); } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index c0f48857c2..085e0fc555 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -78,15 +78,12 @@ public class DownloadServiceDashTest { cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); Runnable pauseAction = - new Runnable() { - @Override - public void run() { - if (pauseDownloadCondition != null) { - try { - pauseDownloadCondition.block(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + () -> { + if (pauseDownloadCondition != null) { + try { + pauseDownloadCondition.block(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } }; @@ -109,55 +106,46 @@ public class DownloadServiceDashTest { fakeStreamKey2 = new StreamKey(0, 1, 0); dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - File actionFile; - try { - actionFile = Util.createTempFile(context, "ExoPlayerTest"); - } catch (IOException e) { - throw new RuntimeException(e); - } - actionFile.delete(); - final DownloadManager dashDownloadManager = - new DownloadManager( - new DownloaderConstructorHelper(cache, fakeDataSourceFactory), - 1, - 3, - actionFile, - DashDownloadAction.DESERIALIZER); - downloadManagerListener = - new TestDownloadManagerListener(dashDownloadManager, dummyMainThread); - dashDownloadManager.addListener(downloadManagerListener); - dashDownloadManager.startDownloads(); - - dashDownloadService = - new DownloadService(DownloadService.FOREGROUND_NOTIFICATION_ID_NONE) { - @Override - protected DownloadManager getDownloadManager() { - return dashDownloadManager; - } - - @Nullable - @Override - protected Scheduler getScheduler() { - return null; - } - }; - dashDownloadService.onCreate(); + () -> { + File actionFile; + try { + actionFile = Util.createTempFile(context, "ExoPlayerTest"); + } catch (IOException e) { + throw new RuntimeException(e); } + actionFile.delete(); + final DownloadManager dashDownloadManager = + new DownloadManager( + new DownloaderConstructorHelper(cache, fakeDataSourceFactory), + 1, + 3, + actionFile, + DashDownloadAction.DESERIALIZER); + downloadManagerListener = + new TestDownloadManagerListener(dashDownloadManager, dummyMainThread); + dashDownloadManager.addListener(downloadManagerListener); + dashDownloadManager.startDownloads(); + + dashDownloadService = + new DownloadService(DownloadService.FOREGROUND_NOTIFICATION_ID_NONE) { + @Override + protected DownloadManager getDownloadManager() { + return dashDownloadManager; + } + + @Nullable + @Override + protected Scheduler getScheduler() { + return null; + } + }; + dashDownloadService.onCreate(); }); } @After public void tearDown() { - dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - dashDownloadService.onDestroy(); - } - }); + dummyMainThread.runOnMainThread(() -> dashDownloadService.onDestroy()); Util.recursiveDelete(tempFolder); dummyMainThread.release(); } @@ -210,13 +198,10 @@ public class DownloadServiceDashTest { private void callDownloadServiceOnStart(final DownloadAction action) { dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - Intent startIntent = - DownloadService.buildAddActionIntent(context, DownloadService.class, action, false); - dashDownloadService.onStartCommand(startIntent, 0, 0); - } + () -> { + Intent startIntent = + DownloadService.buildAddActionIntent(context, DownloadService.class, action, false); + dashDownloadService.onStartCommand(startIntent, 0, 0); }); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 1a80ade01d..dcaecf6d60 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -684,12 +684,7 @@ public final class SsMediaSource extends BaseMediaSource } long nextLoadTimestamp = manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS; long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime()); - manifestRefreshHandler.postDelayed(new Runnable() { - @Override - public void run() { - startLoadingManifest(); - } - }, delayUntilNextLoad); + manifestRefreshHandler.postDelayed(this::startLoadingManifest, delayUntilNextLoad); } private void startLoadingManifest() { 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 abe884ce53..af20c09756 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 @@ -208,6 +208,8 @@ public class PlayerControlView extends FrameLayout { private final Formatter formatter; private final Timeline.Period period; private final Timeline.Window window; + private final Runnable updateProgressAction; + private final Runnable hideAction; private final Drawable repeatOffButtonDrawable; private final Drawable repeatOneButtonDrawable; @@ -236,22 +238,6 @@ public class PlayerControlView extends FrameLayout { private long[] extraAdGroupTimesMs; private boolean[] extraPlayedAdGroups; - private final Runnable updateProgressAction = - new Runnable() { - @Override - public void run() { - updateProgress(); - } - }; - - private final Runnable hideAction = - new Runnable() { - @Override - public void run() { - hide(); - } - }; - public PlayerControlView(Context context) { this(context, null); } @@ -303,6 +289,8 @@ public class PlayerControlView extends FrameLayout { extraPlayedAdGroups = new boolean[0]; componentListener = new ComponentListener(); controlDispatcher = new com.google.android.exoplayer2.DefaultControlDispatcher(); + updateProgressAction = this::updateProgress; + hideAction = this::hide; LayoutInflater.from(context).inflate(controllerLayoutId, this); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 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 f3edacaebc..09bcac8861 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 @@ -203,14 +203,11 @@ public class PlayerNotificationManager { public void onBitmap(final Bitmap bitmap) { if (bitmap != null) { mainHandler.post( - new Runnable() { - @Override - public void run() { - if (player != null - && notificationTag == currentNotificationTag - && isNotificationStarted) { - updateNotification(bitmap); - } + () -> { + if (player != null + && notificationTag == currentNotificationTag + && isNotificationStarted) { + updateNotification(bitmap); } }); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java index fe5d5cbbc5..3f09ac2427 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionView.java @@ -19,7 +19,6 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.content.res.TypedArray; import android.support.annotation.AttrRes; import android.support.annotation.Nullable; @@ -80,13 +79,7 @@ public class TrackSelectionView extends LinearLayout { final TrackSelectionView selectionView = dialogView.findViewById(R.id.exo_track_selection_view); selectionView.init(trackSelector, rendererIndex); - Dialog.OnClickListener okClickListener = - new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - selectionView.applySelection(); - } - }; + Dialog.OnClickListener okClickListener = (dialog, which) -> selectionView.applySelection(); AlertDialog dialog = builder diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index a6f672e54d..9e7d583894 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -469,12 +469,8 @@ public abstract class Action { SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) { player .createMessage( - new Target() { - @Override - public void handleMessage(int messageType, Object payload) - throws ExoPlaybackException { - throw exception; - } + (messageType, payload) -> { + throw exception; }) .send(); } @@ -510,25 +506,14 @@ public abstract class Action { // Schedule one message on the playback thread to pause the player immediately. player .createMessage( - new Target() { - @Override - public void handleMessage(int messageType, Object payload) - throws ExoPlaybackException { - player.setPlayWhenReady(/* playWhenReady= */ false); - } - }) + (messageType, payload) -> player.setPlayWhenReady(/* playWhenReady= */ false)) .setPosition(windowIndex, positionMs) .send(); // Schedule another message on this test thread to continue action schedule. player .createMessage( - new Target() { - @Override - public void handleMessage(int messageType, Object payload) - throws ExoPlaybackException { - nextAction.schedule(player, trackSelector, surface, handler); - } - }) + (messageType, payload) -> + nextAction.schedule(player, trackSelector, surface, handler)) .setPosition(windowIndex, positionMs) .setHandler(new Handler()) .send(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 74fa13ece1..39a95af36d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -610,13 +610,7 @@ public final class ActionSchedule { ActionNode nextAction) { Assertions.checkArgument(nextAction == null); if (callback != null) { - handler.post( - new Runnable() { - @Override - public void run() { - callback.onActionScheduleFinished(); - } - }); + handler.post(() -> callback.onActionScheduleFinished()); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java index 8f65dc876a..858d287196 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java @@ -60,12 +60,9 @@ public final class DummyMainThread { } else { final ConditionVariable finishedCondition = new ConditionVariable(); handler.post( - new Runnable() { - @Override - public void run() { - runnable.run(); - finishedCondition.open(); - } + () -> { + runnable.run(); + finishedCondition.open(); }); assertThat(finishedCondition.block(timeoutMs)).isTrue(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java index 00e2943086..cf8d694286 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java @@ -344,12 +344,7 @@ public abstract class ExoHostedTest player = null; // We post opening of the finished condition so that any events posted to the main thread as a // result of player.release() are guaranteed to be handled before the test returns. - actionHandler.post(new Runnable() { - @Override - public void run() { - testFinished.open(); - } - }); + actionHandler.post(testFinished::open); return true; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index 5ac071d9a2..2a50b2a240 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -33,12 +33,8 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; -import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; @@ -307,18 +303,12 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc renderers = new Renderer[] {new FakeRenderer(supportedFormats)}; } renderersFactory = - new RenderersFactory() { - @Override - public Renderer[] createRenderers( - android.os.Handler eventHandler, - VideoRendererEventListener videoRendererEventListener, - AudioRendererEventListener audioRendererEventListener, - TextOutput textRendererOutput, - MetadataOutput metadataRendererOutput, - DrmSessionManager drmSessionManager) { - return renderers; - } - }; + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput, + drmSessionManager) -> renderers; } if (loadControl == null) { loadControl = new DefaultLoadControl(); @@ -425,35 +415,31 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc */ public ExoPlayerTestRunner start() { handler.post( - new Runnable() { - @Override - public void run() { - try { - player = - new TestSimpleExoPlayer( - context, renderersFactory, trackSelector, loadControl, clock); - player.addListener(ExoPlayerTestRunner.this); - if (eventListener != null) { - player.addListener(eventListener); - } - if (videoRendererEventListener != null) { - player.addVideoDebugListener(videoRendererEventListener); - } - if (audioRendererEventListener != null) { - player.addAudioDebugListener(audioRendererEventListener); - } - if (analyticsListener != null) { - player.addAnalyticsListener(analyticsListener); - } - player.setPlayWhenReady(true); - if (actionSchedule != null) { - actionSchedule.start( - player, trackSelector, null, handler, ExoPlayerTestRunner.this); - } - player.prepare(mediaSource); - } catch (Exception e) { - handleException(e); + () -> { + try { + player = + new TestSimpleExoPlayer( + context, renderersFactory, trackSelector, loadControl, clock); + player.addListener(ExoPlayerTestRunner.this); + if (eventListener != null) { + player.addListener(eventListener); } + if (videoRendererEventListener != null) { + player.addVideoDebugListener(videoRendererEventListener); + } + if (audioRendererEventListener != null) { + player.addAudioDebugListener(audioRendererEventListener); + } + if (analyticsListener != null) { + player.addAnalyticsListener(analyticsListener); + } + player.setPlayWhenReady(true); + if (actionSchedule != null) { + actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this); + } + player.prepare(mediaSource); + } catch (Exception e) { + handleException(e); } }); return this; @@ -579,20 +565,18 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc // Private implementation details. private void release() throws InterruptedException { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (player != null) { - player.release(); + handler.post( + () -> { + try { + if (player != null) { + player.release(); + } + } catch (Exception e) { + handleException(e); + } finally { + playerThread.quit(); } - } catch (Exception e) { - handleException(e); - } finally { - playerThread.quit(); - } - } - }); + }); playerThread.join(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java index 8cef80766b..4e3713a4c6 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java @@ -93,13 +93,7 @@ public class FakeMediaPeriod implements MediaPeriod { public synchronized void setPreparationComplete() { deferOnPrepared = false; if (playerHandler != null && prepareCallback != null) { - playerHandler.post( - new Runnable() { - @Override - public void run() { - finishPreparation(); - } - }); + playerHandler.post(this::finishPreparation); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 2dfc45d71f..90e86d6c5a 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -149,15 +149,12 @@ public class FakeMediaSource extends BaseMediaSource { public synchronized void setNewSourceInfo(final Timeline newTimeline, final Object newManifest) { if (sourceInfoRefreshHandler != null) { sourceInfoRefreshHandler.post( - new Runnable() { - @Override - public void run() { - assertThat(releasedSource).isFalse(); - assertThat(preparedSource).isTrue(); - timeline = newTimeline; - manifest = newManifest; - finishSourcePreparation(); - } + () -> { + assertThat(releasedSource).isFalse(); + assertThat(preparedSource).isTrue(); + timeline = newTimeline; + manifest = newManifest; + finishSourcePreparation(); }); } else { timeline = newTimeline; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java index fde92d690d..f36859d1ab 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java @@ -118,13 +118,11 @@ public final class HostActivity extends Activity implements SurfaceHolder.Callba forcedStopped = false; hostedTestStarted = false; - runOnUiThread(new Runnable() { - @Override - public void run() { - HostActivity.this.hostedTest = hostedTest; - maybeStartHostedTest(); - } - }); + runOnUiThread( + () -> { + HostActivity.this.hostedTest = hostedTest; + maybeStartHostedTest(); + }); if (!hostedTestStartedCondition.block(START_TIMEOUT_MS)) { String message = @@ -145,12 +143,7 @@ public final class HostActivity extends Activity implements SurfaceHolder.Callba fail(message); } } else { - runOnUiThread(new Runnable() { - @Override - public void run() { - hostedTest.forceStop(); - } - }); + runOnUiThread(hostedTest::forceStop); String message = "Test timed out after " + timeoutMs + " ms."; Log.e(TAG, message); if (failOnTimeout) { 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 725753ce46..90e70e4538 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 @@ -131,13 +131,7 @@ public final class FakeClockTest { private static void waitForHandler(HandlerWrapper handler) { final ConditionVariable handlerFinished = new ConditionVariable(); - handler.post( - new Runnable() { - @Override - public void run() { - handlerFinished.open(); - } - }); + handler.post(handlerFinished::open); handlerFinished.block(); } diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java index 75c6f886c2..99469295bb 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java @@ -65,11 +65,8 @@ public final class FakeDataSetTest { public void testSegmentTypes() { byte[] testData = TestUtil.buildTestData(3); Runnable runnable = - new Runnable() { - @Override - public void run() { - // Do nothing. - } + () -> { + // Do nothing. }; IOException exception = new IOException(); FakeDataSet fakeDataSet = diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 3fffdb2696..4f9012ab27 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -97,16 +97,13 @@ public class MediaSourceTestRunner { final Throwable[] throwable = new Throwable[1]; final ConditionVariable finishedCondition = new ConditionVariable(); playbackHandler.post( - new Runnable() { - @Override - public void run() { - try { - runnable.run(); - } catch (Throwable e) { - throwable[0] = e; - } finally { - finishedCondition.open(); - } + () -> { + try { + runnable.run(); + } catch (Throwable e) { + throwable[0] = e; + } finally { + finishedCondition.open(); } }); assertThat(finishedCondition.block(TIMEOUT_MS)).isTrue(); @@ -123,22 +120,19 @@ public class MediaSourceTestRunner { public Timeline prepareSource() throws IOException { final IOException[] prepareError = new IOException[1]; runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - mediaSource.prepareSource( - player, - /* isTopLevelSource= */ true, - mediaSourceListener, - /* mediaTransferListener= */ null); - try { - // TODO: This only catches errors that are set synchronously in prepareSource. To - // capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the - // first call to onSourceInfoRefreshed. - mediaSource.maybeThrowSourceInfoRefreshError(); - } catch (IOException e) { - prepareError[0] = e; - } + () -> { + mediaSource.prepareSource( + player, + /* isTopLevelSource= */ true, + mediaSourceListener, + /* mediaTransferListener= */ null); + try { + // TODO: This only catches errors that are set synchronously in prepareSource. To + // capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the + // first call to onSourceInfoRefreshed. + mediaSource.maybeThrowSourceInfoRefreshError(); + } catch (IOException e) { + prepareError[0] = e; } }); if (prepareError[0] != null) { @@ -156,13 +150,7 @@ public class MediaSourceTestRunner { */ public MediaPeriod createPeriod(final MediaPeriodId periodId) { final MediaPeriod[] holder = new MediaPeriod[1]; - runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - holder[0] = mediaSource.createPeriod(periodId, allocator); - } - }); + runOnPlaybackThread(() -> holder[0] = mediaSource.createPeriod(periodId, allocator)); assertThat(holder[0]).isNotNull(); return holder[0]; } @@ -179,24 +167,21 @@ public class MediaSourceTestRunner { final ConditionVariable prepareCalled = new ConditionVariable(); final CountDownLatch preparedCountDown = new CountDownLatch(1); runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - mediaPeriod.prepare( - new MediaPeriod.Callback() { - @Override - public void onPrepared(MediaPeriod mediaPeriod) { - preparedCountDown.countDown(); - } + () -> { + mediaPeriod.prepare( + new MediaPeriod.Callback() { + @Override + public void onPrepared(MediaPeriod mediaPeriod1) { + preparedCountDown.countDown(); + } - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - // Do nothing. - } - }, - positionUs); - prepareCalled.open(); - } + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + // Do nothing. + } + }, + positionUs); + prepareCalled.open(); }); prepareCalled.block(); return preparedCountDown; @@ -208,13 +193,7 @@ public class MediaSourceTestRunner { * @param mediaPeriod The {@link MediaPeriod} to release. */ public void releasePeriod(final MediaPeriod mediaPeriod) { - runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - mediaSource.releasePeriod(mediaPeriod); - } - }); + runOnPlaybackThread(() -> mediaSource.releasePeriod(mediaPeriod)); } /** @@ -222,13 +201,7 @@ public class MediaSourceTestRunner { * thread. */ public void releaseSource() { - runOnPlaybackThread( - new Runnable() { - @Override - public void run() { - mediaSource.releaseSource(mediaSourceListener); - } - }); + runOnPlaybackThread(() -> mediaSource.releaseSource(mediaSourceListener)); } /** diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java index b624c49350..38bf136f11 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java @@ -81,12 +81,9 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen downloadFinishedCondition = new CountDownLatch(1); } dummyMainThread.runOnMainThread( - new Runnable() { - @Override - public void run() { - if (downloadManager.isIdle()) { - downloadFinishedCondition.countDown(); - } + () -> { + if (downloadManager.isIdle()) { + downloadFinishedCondition.countDown(); } }); assertThat(downloadFinishedCondition.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue(); From b58f6940ebdfd966d6679beb2d9c985ce473fd7d Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 17 Aug 2018 12:57:54 -0700 Subject: [PATCH 03/42] Add VideoFrameMetadataListener ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209193233 --- .../ext/vp9/LibvpxVideoRenderer.java | 36 ++++ .../java/com/google/android/exoplayer2/C.java | 8 + .../com/google/android/exoplayer2/Player.java | 20 +++ .../android/exoplayer2/SimpleExoPlayer.java | 32 ++++ .../audio/MediaCodecAudioRenderer.java | 14 +- .../mediacodec/MediaCodecRenderer.java | 112 +++++++----- .../exoplayer2/util/TimedValueQueue.java | 160 ++++++++++++++++++ .../video/MediaCodecVideoRenderer.java | 35 +++- .../video/VideoFrameMetadataListener.java | 31 ++++ .../exoplayer2/util/TimedValueQueueTest.java | 112 ++++++++++++ .../testutil/DebugRenderersFactory.java | 26 ++- 11 files changed, 529 insertions(+), 57 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 08c413aba7..f0986d08be 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -39,8 +39,10 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import java.lang.annotation.Retention; @@ -109,11 +111,14 @@ public class LibvpxVideoRenderer extends BaseRenderer { private final boolean playClearSamplesWithoutKeys; private final EventDispatcher eventDispatcher; private final FormatHolder formatHolder; + private final TimedValueQueue formatQueue; private final DecoderInputBuffer flagsOnlyBuffer; private final DrmSessionManager drmSessionManager; private final boolean useSurfaceYuvOutput; private Format format; + private Format pendingFormat; + private Format outputFormat; private VpxDecoder decoder; private VpxInputBuffer inputBuffer; private VpxOutputBuffer outputBuffer; @@ -142,6 +147,8 @@ public class LibvpxVideoRenderer extends BaseRenderer { private int consecutiveDroppedFrameCount; private int buffersInCodecCount; private long lastRenderTimeUs; + private long outputStreamOffsetUs; + private VideoFrameMetadataListener frameMetadataListener; protected DecoderCounters decoderCounters; @@ -219,6 +226,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { joiningDeadlineMs = C.TIME_UNSET; clearReportedVideoSize(); formatHolder = new FormatHolder(); + formatQueue = new TimedValueQueue<>(); flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); eventDispatcher = new EventDispatcher(eventHandler, eventListener); outputMode = VpxDecoder.OUTPUT_MODE_NONE; @@ -328,6 +336,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { } else { joiningDeadlineMs = C.TIME_UNSET; } + formatQueue.clear(); } @Override @@ -371,6 +380,12 @@ public class LibvpxVideoRenderer extends BaseRenderer { } } + @Override + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { + outputStreamOffsetUs = offsetUs; + super.onStreamChanged(formats, offsetUs); + } + /** * Called when a decoder has been created and configured. * @@ -437,6 +452,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { Format oldFormat = format; format = newFormat; + pendingFormat = newFormat; boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData); @@ -629,6 +645,8 @@ public class LibvpxVideoRenderer extends BaseRenderer { setOutput((Surface) message, null); } else if (messageType == MSG_SET_OUTPUT_BUFFER_RENDERER) { setOutput(null, (VpxOutputBufferRenderer) message); + } else if (messageType == C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) { + frameMetadataListener = (VideoFrameMetadataListener) message; } else { super.handleMessage(messageType, message); } @@ -772,6 +790,10 @@ public class LibvpxVideoRenderer extends BaseRenderer { if (waitingForKeys) { return false; } + if (pendingFormat != null) { + formatQueue.add(inputBuffer.timeUs, pendingFormat); + pendingFormat = null; + } inputBuffer.flip(); inputBuffer.colorInfo = formatHolder.format.colorInfo; onQueueInputBuffer(inputBuffer); @@ -851,11 +873,21 @@ public class LibvpxVideoRenderer extends BaseRenderer { return false; } + long presentationTimeUs = outputBuffer.timeUs - outputStreamOffsetUs; + Format format = formatQueue.pollFloor(presentationTimeUs); + if (format != null) { + outputFormat = format; + } + long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; boolean isStarted = getState() == STATE_STARTED; if (!renderedFirstFrame || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { + if (frameMetadataListener != null) { + frameMetadataListener.onVideoFrameAboutToBeRendered( + presentationTimeUs, System.nanoTime(), outputFormat); + } renderOutputBuffer(outputBuffer); return true; } @@ -873,6 +905,10 @@ public class LibvpxVideoRenderer extends BaseRenderer { } if (earlyUs < 30000) { + if (frameMetadataListener != null) { + frameMetadataListener.onVideoFrameAboutToBeRendered( + presentationTimeUs, System.nanoTime(), outputFormat); + } renderOutputBuffer(outputBuffer); return true; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 87499a9cb1..6930975fca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -26,6 +26,7 @@ import android.view.Surface; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.UUID; @@ -733,6 +734,13 @@ public final class C { */ public static final int MSG_SET_AUX_EFFECT_INFO = 5; + /** + * The type of a message that can be passed to a video {@link Renderer} via {@link + * ExoPlayer#createMessage(Target)}. The message payload should be a {@link + * VideoFrameMetadataListener} instance, or null. + */ + public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = 6; + /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * {@link Renderer}s. These custom constants must be greater than or equal to this value. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 87aec0c253..4711933f17 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -165,6 +166,25 @@ public interface Player { */ void removeVideoListener(VideoListener listener); + /** + * Sets a listener to receive video frame metadata events. + * + *

This method is intended to be called by the same component that sets the {@link Surface} + * onto which video will be rendered. If using ExoPlayer's standard UI components, this method + * should not be called directly from application code. + * + * @param listener The listener. + */ + void setVideoFrameMetadataListener(VideoFrameMetadataListener listener); + + /** + * Clears the listener which receives video frame metadata events if it matches the one passed. + * Else does nothing. + * + * @param listener The listener to clear. + */ + void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener); + /** * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} * currently set on the player. 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 055cf1de17..65d1113be6 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 @@ -51,6 +51,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.util.ArrayList; import java.util.Collections; @@ -105,6 +106,7 @@ public class SimpleExoPlayer private float audioVolume; private MediaSource mediaSource; private List currentCues; + private VideoFrameMetadataListener videoFrameMetadataListener; /** * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. @@ -598,6 +600,36 @@ public class SimpleExoPlayer videoListeners.remove(listener); } + @Override + public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { + videoFrameMetadataListener = listener; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + player + .createMessage(renderer) + .setType(C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(listener) + .send(); + } + } + } + + @Override + public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { + if (videoFrameMetadataListener != listener) { + return; + } + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + player + .createMessage(renderer) + .setType(C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(null) + .send(); + } + } + } + /** * Sets a listener to receive video events, removing all existing listeners. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 1197cb5a71..1d3e65f7ff 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -547,9 +547,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, - ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, - boolean shouldSkip) throws ExoPlaybackException { + protected boolean processOutputBuffer( + long positionUs, + long elapsedRealtimeUs, + MediaCodec codec, + ByteBuffer buffer, + int bufferIndex, + int bufferFlags, + long bufferPresentationTimeUs, + boolean shouldSkip, + Format format) + throws ExoPlaybackException { if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // Discard output buffers from the passthrough (raw) decoder containing codec specific data. codec.releaseOutputBuffer(bufferIndex, false); 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 3630977fca..0a80780148 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 @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NalUnitUtil; +import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; @@ -272,10 +273,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final DecoderInputBuffer buffer; private final DecoderInputBuffer flagsOnlyBuffer; private final FormatHolder formatHolder; + private final TimedValueQueue formatQueue; private final List decodeOnlyPresentationTimestamps; private final MediaCodec.BufferInfo outputBufferInfo; private Format format; + private Format pendingFormat; + private Format outputFormat; private DrmSession drmSession; private DrmSession pendingDrmSession; private MediaCodec codec; @@ -344,6 +348,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); formatHolder = new FormatHolder(); + formatQueue = new TimedValueQueue<>(); decodeOnlyPresentationTimestamps = new ArrayList<>(); outputBufferInfo = new MediaCodec.BufferInfo(); codecReconfigurationState = RECONFIGURATION_STATE_NONE; @@ -501,6 +506,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codec != null) { flushCodec(); } + formatQueue.clear(); } @Override @@ -956,6 +962,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (buffer.isDecodeOnly()) { decodeOnlyPresentationTimestamps.add(presentationTimeUs); } + if (pendingFormat != null) { + formatQueue.add(presentationTimeUs, pendingFormat); + pendingFormat = null; + } buffer.flip(); onQueueInputBuffer(buffer); @@ -1012,6 +1022,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { Format oldFormat = format; format = newFormat; + pendingFormat = newFormat; boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData); @@ -1234,35 +1245,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); } - if (outputIndex >= 0) { - // We've dequeued a buffer. - if (shouldSkipAdaptationWorkaroundOutputBuffer) { - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codec.releaseOutputBuffer(outputIndex, false); + if (outputIndex < 0) { + if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) { + processOutputFormat(); + return true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) { + processOutputBuffersChanged(); return true; - } else if (outputBufferInfo.size == 0 - && (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - // The dequeued buffer indicates the end of the stream. Process it immediately. - processEndOfStream(); - return false; - } else { - this.outputIndex = outputIndex; - outputBuffer = getOutputBuffer(outputIndex); - // The dequeued buffer is a media buffer. Do some initial setup. - // It will be processed by calling processOutputBuffer (possibly multiple times). - if (outputBuffer != null) { - outputBuffer.position(outputBufferInfo.offset); - outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); - } - shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); } - } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) { - processOutputFormat(); - return true; - } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) { - processOutputBuffersChanged(); - return true; - } else /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ { + /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ if (codecNeedsEosPropagationWorkaround && (inputStreamEnded || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { @@ -1270,6 +1261,32 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } return false; } + + // We've dequeued a buffer. + if (shouldSkipAdaptationWorkaroundOutputBuffer) { + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codec.releaseOutputBuffer(outputIndex, false); + return true; + } else if (outputBufferInfo.size == 0 + && (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + // The dequeued buffer indicates the end of the stream. Process it immediately. + processEndOfStream(); + return false; + } + + this.outputIndex = outputIndex; + outputBuffer = getOutputBuffer(outputIndex); + // The dequeued buffer is a media buffer. Do some initial setup. + // It will be processed by calling processOutputBuffer (possibly multiple times). + if (outputBuffer != null) { + outputBuffer.position(outputBufferInfo.offset); + outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); + } + shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); + Format format = formatQueue.pollFloor(outputBufferInfo.presentationTimeUs); + if (format != null) { + outputFormat = format; + } } boolean processedOutputBuffer; @@ -1284,7 +1301,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, - shouldSkipOutputBuffer); + shouldSkipOutputBuffer, + outputFormat); } catch (IllegalStateException e) { processEndOfStream(); if (outputStreamEnded) { @@ -1303,7 +1321,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, - shouldSkipOutputBuffer); + shouldSkipOutputBuffer, + outputFormat); } if (processedOutputBuffer) { @@ -1348,36 +1367,43 @@ public abstract class MediaCodecRenderer extends BaseRenderer { /** * Processes an output media buffer. - *

- * When a new {@link ByteBuffer} is passed to this method its position and limit delineate the + * + *

When a new {@link ByteBuffer} is passed to this method its position and limit delineate the * data to be processed. The return value indicates whether the buffer was processed in full. If * true is returned then the next call to this method will receive a new buffer to be processed. * If false is returned then the same buffer will be passed to the next call. An implementation of * this method is free to modify the buffer and can assume that the buffer will not be externally * modified between successive calls. Hence an implementation can, for example, modify the * buffer's position to keep track of how much of the data it has processed. - *

- * Note that the first call to this method following a call to - * {@link #onPositionReset(long, boolean)} will always receive a new {@link ByteBuffer} to be - * processed. * - * @param positionUs The current media time in microseconds, measured at the start of the - * current iteration of the rendering loop. - * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, - * measured at the start of the current iteration of the rendering loop. + *

Note that the first call to this method following a call to {@link #onPositionReset(long, + * boolean)} will always receive a new {@link ByteBuffer} to be processed. + * + * @param positionUs The current media time in microseconds, measured at the start of the current + * iteration of the rendering loop. + * @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the + * start of the current iteration of the rendering loop. * @param codec The {@link MediaCodec} instance. * @param buffer The output buffer to process. * @param bufferIndex The index of the output buffer. * @param bufferFlags The flags attached to the output buffer. * @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds. * @param shouldSkip Whether the buffer should be skipped (i.e. not rendered). - * + * @param format The format associated with the buffer. * @return Whether the output buffer was fully processed (e.g. rendered or skipped). * @throws ExoPlaybackException If an error occurs processing the output buffer. */ - protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, - MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, - long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; + protected abstract boolean processOutputBuffer( + long positionUs, + long elapsedRealtimeUs, + MediaCodec codec, + ByteBuffer buffer, + int bufferIndex, + int bufferFlags, + long bufferPresentationTimeUs, + boolean shouldSkip, + Format format) + throws ExoPlaybackException; /** * Incrementally renders any remaining output. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java new file mode 100644 index 0000000000..160db74eda --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import android.support.annotation.Nullable; +import java.util.Arrays; +import org.checkerframework.checker.nullness.compatqual.NullableType; + +/** A utility class to keep a queue of values with timestamps. This class is thread safe. */ +public final class TimedValueQueue { + private static final int INITIAL_BUFFER_SIZE = 10; + + // Looping buffer for timestamps and values + private long[] timestamps; + private @NullableType V[] values; + private int first; + private int size; + + public TimedValueQueue() { + this(INITIAL_BUFFER_SIZE); + } + + /** Creates a TimedValueBuffer with the given initial buffer size. */ + public TimedValueQueue(int initialBufferSize) { + timestamps = new long[initialBufferSize]; + values = newArray(initialBufferSize); + } + + /** + * Associates the specified value with the specified timestamp. All new values should have a + * greater timestamp than the previously added values. Otherwise all values are removed before + * adding the new one. + */ + public synchronized void add(long timestamp, V value) { + clearBufferOnTimeDiscontinuity(timestamp); + doubleCapacityIfFull(); + addUnchecked(timestamp, value); + } + + /** Removes all of the values. */ + public synchronized void clear() { + first = 0; + size = 0; + Arrays.fill(values, null); + } + + /** Returns number of the values buffered. */ + public synchronized int size() { + return size; + } + + /** + * Returns the value with the greatest timestamp which is less than or equal to the given + * timestamp. Removes all older values including the returned one from the buffer. + * + * @param timestamp The timestamp value. + * @return The value with the greatest timestamp which is less than or equal to the given + * timestamp or null if there is no such value. + * @see #poll(long) + */ + public synchronized @Nullable V pollFloor(long timestamp) { + return poll(timestamp, /* onlyOlder= */ true); + } + + /** + * Returns the value with the closest timestamp to the given timestamp. Removes all older values + * including the returned one from the buffer. + * + * @param timestamp The timestamp value. + * @return The value with the closest timestamp or null if the buffer is empty. + * @see #pollFloor(long) + */ + public synchronized @Nullable V poll(long timestamp) { + return poll(timestamp, /* onlyOlder= */ false); + } + + /** + * Returns the value with the closest timestamp to the given timestamp. Removes all older values + * including the returned one from the buffer. + * + * @param timestamp The timestamp value. + * @param onlyOlder Whether this method can return a new value in case its timestamp value is + * closest to {@code timestamp}. + * @return The value with the closest timestamp or null if the buffer is empty or there is no + * older value and {@code onlyOlder} is true. + */ + private @Nullable V poll(long timestamp, boolean onlyOlder) { + V value = null; + long previousTimeDiff = Long.MAX_VALUE; + while (size > 0) { + long timeDiff = timestamp - timestamps[first]; + if (timeDiff < 0 && (onlyOlder || -timeDiff >= previousTimeDiff)) { + break; + } + previousTimeDiff = timeDiff; + value = values[first]; + values[first] = null; + first = (first + 1) % values.length; + size--; + } + return value; + } + + private void clearBufferOnTimeDiscontinuity(long timestamp) { + if (size > 0) { + int last = (first + size - 1) % values.length; + if (timestamp <= timestamps[last]) { + clear(); + } + } + } + + private void doubleCapacityIfFull() { + int capacity = values.length; + if (size < capacity) { + return; + } + int newCapacity = capacity * 2; + long[] newTimestamps = new long[newCapacity]; + V[] newValues = newArray(newCapacity); + // Reset the loop starting index to 0 while coping to the new buffer. + // First copy the values from 'first' index to the end of original array. + int length = capacity - first; + System.arraycopy(timestamps, first, newTimestamps, 0, length); + System.arraycopy(values, first, newValues, 0, length); + // Then the values from index 0 to 'first' index. + if (first > 0) { + System.arraycopy(timestamps, 0, newTimestamps, length, first); + System.arraycopy(values, 0, newValues, length, first); + } + timestamps = newTimestamps; + values = newValues; + first = 0; + } + + private void addUnchecked(long timestamp, V value) { + int next = (first + size) % values.length; + timestamps[next] = timestamp; + values[next] = value; + size++; + } + + @SuppressWarnings("unchecked") + private static V[] newArray(int length) { + return (V[]) new Object[length]; + } +} 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 181232b7b2..0c3cd74b74 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 @@ -136,6 +136,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private long lastInputTimeUs; private long outputStreamOffsetUs; private int pendingOutputStreamOffsetCount; + private @Nullable VideoFrameMetadataListener frameMetadataListener; /** * @param context A context. @@ -386,6 +387,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (codec != null) { codec.setVideoScalingMode(scalingMode); } + } else if (messageType == C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) { + frameMetadataListener = (VideoFrameMetadataListener) message; } else { super.handleMessage(messageType, message); } @@ -587,9 +590,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, - ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, - boolean shouldSkip) throws ExoPlaybackException { + protected boolean processOutputBuffer( + long positionUs, + long elapsedRealtimeUs, + MediaCodec codec, + ByteBuffer buffer, + int bufferIndex, + int bufferFlags, + long bufferPresentationTimeUs, + boolean shouldSkip, + Format format) + throws ExoPlaybackException { if (initialPositionUs == C.TIME_UNSET) { initialPositionUs = positionUs; } @@ -616,8 +627,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (!renderedFirstFrame || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { + long releaseTimeNs = System.nanoTime(); + notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format); if (Util.SDK_INT >= 21) { - renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime()); + renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, releaseTimeNs); } else { renderOutputBuffer(codec, bufferIndex, presentationTimeUs); } @@ -653,6 +666,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (Util.SDK_INT >= 21) { // Let the underlying framework time the release. if (earlyUs < 50000) { + notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format); renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs); return true; } @@ -670,6 +684,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return false; } } + notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format); renderOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } @@ -679,10 +694,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return false; } + private void notifyFrameMetadataListener( + long presentationTimeUs, long releaseTimeNs, Format format) { + if (frameMetadataListener != null) { + frameMetadataListener.onVideoFrameAboutToBeRendered( + presentationTimeUs, releaseTimeNs, format); + } + } + /** * Returns the offset that should be subtracted from {@code bufferPresentationTimeUs} in {@link - * #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, long, boolean)} to get the - * playback position with respect to the media. + * #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, long, boolean, Format)} to + * get the playback position with respect to the media. */ protected long getOutputStreamOffsetUs() { return outputStreamOffsetUs; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java new file mode 100644 index 0000000000..b467d0f421 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import com.google.android.exoplayer2.Format; + +/** A listener for metadata corresponding to video frame being rendered. */ +public interface VideoFrameMetadataListener { + /** + * Called when the video frame about to be rendered. This method is called on the playback thread. + * + * @param presentationTimeUs The presentation time of the output buffer, in microseconds. + * @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds. + * If the platform API version of the device is less than 21, then this is the best effort. + * @param format The format associated with the frame. + */ + void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, Format format); +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java new file mode 100644 index 0000000000..ca34bc3216 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link TimedValueQueue}. */ +@RunWith(RobolectricTestRunner.class) +public class TimedValueQueueTest { + + private TimedValueQueue queue; + + @Before + public void setUp() throws Exception { + queue = new TimedValueQueue<>(); + } + + @Test + public void testAddAndPollValues() { + queue.add(0, "a"); + queue.add(1, "b"); + queue.add(2, "c"); + assertThat(queue.poll(0)).isEqualTo("a"); + assertThat(queue.poll(1)).isEqualTo("b"); + assertThat(queue.poll(2)).isEqualTo("c"); + } + + @Test + public void testBufferCapacityIncreasesAutomatically() { + queue = new TimedValueQueue<>(1); + for (int i = 0; i < 20; i++) { + queue.add(i, "" + i); + if ((i & 1) == 1) { + assertThat(queue.poll(0)).isEqualTo("" + (i / 2)); + } + } + assertThat(queue.size()).isEqualTo(10); + } + + @Test + public void testTimeDiscontinuityClearsValues() { + queue.add(1, "b"); + queue.add(2, "c"); + queue.add(0, "a"); + assertThat(queue.size()).isEqualTo(1); + assertThat(queue.poll(0)).isEqualTo("a"); + } + + @Test + public void testTimeDiscontinuityOnFullBufferClearsValues() { + queue = new TimedValueQueue<>(2); + queue.add(1, "b"); + queue.add(3, "c"); + queue.add(2, "a"); + assertThat(queue.size()).isEqualTo(1); + assertThat(queue.poll(2)).isEqualTo("a"); + } + + @Test + public void testPollReturnsClosestValue() { + queue.add(0, "a"); + queue.add(3, "b"); + assertThat(queue.poll(2)).isEqualTo("b"); + assertThat(queue.size()).isEqualTo(0); + } + + @Test + public void testPollRemovesPreviousValues() { + queue.add(0, "a"); + queue.add(1, "b"); + queue.add(2, "c"); + assertThat(queue.poll(1)).isEqualTo("b"); + assertThat(queue.size()).isEqualTo(1); + } + + @Test + public void testPollFloorReturnsClosestPreviousValue() { + queue.add(0, "a"); + queue.add(3, "b"); + assertThat(queue.pollFloor(2)).isEqualTo("a"); + assertThat(queue.pollFloor(2)).isEqualTo(null); + assertThat(queue.pollFloor(3)).isEqualTo("b"); + assertThat(queue.size()).isEqualTo(0); + } + + @Test + public void testPollFloorRemovesPreviousValues() { + queue.add(0, "a"); + queue.add(1, "b"); + queue.add(2, "c"); + assertThat(queue.pollFloor(1)).isEqualTo("b"); + assertThat(queue.size()).isEqualTo(1); + } +} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java index 02a8a0597d..627b5b72f3 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java @@ -126,17 +126,33 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { } @Override - protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, - ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, - boolean shouldSkip) throws ExoPlaybackException { + protected boolean processOutputBuffer( + long positionUs, + long elapsedRealtimeUs, + MediaCodec codec, + ByteBuffer buffer, + int bufferIndex, + int bufferFlags, + long bufferPresentationTimeUs, + boolean shouldSkip, + Format format) + throws ExoPlaybackException { if (skipToPositionBeforeRenderingFirstFrame && bufferPresentationTimeUs < positionUs) { // After the codec has been initialized, don't render the first frame until we've caught up // to the playback position. Else test runs on devices that do not support dummy surface // will drop frames between rendering the first one and catching up [Internal: b/66494991]. shouldSkip = true; } - return super.processOutputBuffer(positionUs, elapsedRealtimeUs, codec, buffer, bufferIndex, - bufferFlags, bufferPresentationTimeUs, shouldSkip); + return super.processOutputBuffer( + positionUs, + elapsedRealtimeUs, + codec, + buffer, + bufferIndex, + bufferFlags, + bufferPresentationTimeUs, + shouldSkip, + format); } @Override From f64ec43acd403c2d36b98f3255005a387696082e Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 20 Aug 2018 03:17:29 -0700 Subject: [PATCH 04/42] Remove usage of deprecated method from the Demo app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209390036 --- .../exoplayer2/demo/PlayerActivity.java | 6 +-- .../exoplayer2/source/hls/HlsMediaSource.java | 30 +++++------ .../DefaultHlsPlaylistParserFactory.java | 54 +++++++++++++++++++ .../playlist/HlsPlaylistParserFactory.java | 14 ----- 4 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index ad08fb990c..3f02023ec5 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -60,7 +60,7 @@ import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; +import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistParserFactory; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; @@ -490,8 +490,8 @@ public class PlayerActivity extends Activity .createMediaSource(uri); case C.TYPE_HLS: return new HlsMediaSource.Factory(dataSourceFactory) - .setPlaylistParser( - new FilteringManifestParser<>(new HlsPlaylistParser(), getOfflineStreamKeys(uri))) + .setPlaylistParserFactory( + new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri))) .createMediaSource(uri); case C.TYPE_OTHER: return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 1000a38820..670554fe73 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistParserFactory; import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; @@ -62,7 +63,7 @@ public final class HlsMediaSource extends BaseMediaSource private final HlsDataSourceFactory hlsDataSourceFactory; private HlsExtractorFactory extractorFactory; - private @Nullable ParsingLoadable.Parser playlistParser; + private @Nullable HlsPlaylistParserFactory playlistParserFactory; private @Nullable HlsPlaylistTracker playlistTracker; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; @@ -164,23 +165,19 @@ public final class HlsMediaSource extends BaseMediaSource } /** - * Sets the parser to parse HLS playlists. The default is an instance of {@link - * HlsPlaylistParser}. + * Sets the factory from which playlist parsers will be obtained. The default value is created + * by calling {@link DefaultHlsPlaylistParserFactory#DefaultHlsPlaylistParserFactory()}. * *

Must not be called after calling {@link #setPlaylistTracker} on the same builder. * - * @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists. + * @param playlistParserFactory An {@link HlsPlaylistParserFactory}. * @return This factory, for convenience. * @throws IllegalStateException If one of the {@code create} methods has already been called. - * @deprecated Use {@link #setPlaylistTracker(HlsPlaylistTracker)} instead. Using this method - * prevents support for attributes that are carried over from the master playlist to the - * media playlists. */ - @Deprecated - public Factory setPlaylistParser(ParsingLoadable.Parser playlistParser) { + public Factory setPlaylistParserFactory(HlsPlaylistParserFactory playlistParserFactory) { Assertions.checkState(!isCreateCalled); Assertions.checkState(playlistTracker == null, "A playlist tracker has already been set."); - this.playlistParser = Assertions.checkNotNull(playlistParser); + this.playlistParserFactory = Assertions.checkNotNull(playlistParserFactory); return this; } @@ -189,7 +186,7 @@ public final class HlsMediaSource extends BaseMediaSource * DefaultHlsPlaylistTracker}. Playlist trackers must not be shared by {@link HlsMediaSource} * instances. * - *

Must not be called after calling {@link #setPlaylistParser} on the same builder. + *

Must not be called after calling {@link #setPlaylistParserFactory} on the same builder. * * @param playlistTracker A tracker for HLS playlists. * @return This factory, for convenience. @@ -197,7 +194,8 @@ public final class HlsMediaSource extends BaseMediaSource */ public Factory setPlaylistTracker(HlsPlaylistTracker playlistTracker) { Assertions.checkState(!isCreateCalled); - Assertions.checkState(playlistParser == null, "A playlist parser has already been set."); + Assertions.checkState( + playlistParserFactory == null, "A playlist parser factory has already been set."); this.playlistTracker = Assertions.checkNotNull(playlistTracker); return this; } @@ -244,14 +242,16 @@ public final class HlsMediaSource extends BaseMediaSource public HlsMediaSource createMediaSource(Uri playlistUri) { isCreateCalled = true; if (playlistTracker == null) { - if (playlistParser == null) { + if (playlistParserFactory == null) { playlistTracker = new DefaultHlsPlaylistTracker( - hlsDataSourceFactory, loadErrorHandlingPolicy, HlsPlaylistParserFactory.DEFAULT); + hlsDataSourceFactory, + loadErrorHandlingPolicy, + new DefaultHlsPlaylistParserFactory()); } else { playlistTracker = new DefaultHlsPlaylistTracker( - hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParser); + hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory); } } return new HlsMediaSource( diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java new file mode 100644 index 0000000000..9058980c73 --- /dev/null +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.hls.playlist; + +import com.google.android.exoplayer2.offline.FilteringManifestParser; +import com.google.android.exoplayer2.offline.StreamKey; +import com.google.android.exoplayer2.upstream.ParsingLoadable; +import java.util.Collections; +import java.util.List; + +/** Default implementation for {@link HlsPlaylistParserFactory}. */ +public final class DefaultHlsPlaylistParserFactory implements HlsPlaylistParserFactory { + + private final List streamKeys; + + /** Creates an instance that does not filter any parsing results. */ + public DefaultHlsPlaylistParserFactory() { + this(Collections.emptyList()); + } + + /** + * Creates an instance that filters the parsing results using the given {@code streamKeys}. + * + * @param streamKeys See {@link + * FilteringManifestParser#FilteringManifestParser(ParsingLoadable.Parser, List)}. + */ + public DefaultHlsPlaylistParserFactory(List streamKeys) { + this.streamKeys = streamKeys; + } + + @Override + public ParsingLoadable.Parser createPlaylistParser() { + return new FilteringManifestParser<>(new HlsPlaylistParser(), streamKeys); + } + + @Override + public ParsingLoadable.Parser createPlaylistParser( + HlsMasterPlaylist masterPlaylist) { + return new FilteringManifestParser<>(new HlsPlaylistParser(masterPlaylist), streamKeys); + } +} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java index 717825c168..814060bf7d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParserFactory.java @@ -20,20 +20,6 @@ import com.google.android.exoplayer2.upstream.ParsingLoadable; /** Factory for {@link HlsPlaylist} parsers. */ public interface HlsPlaylistParserFactory { - HlsPlaylistParserFactory DEFAULT = - new HlsPlaylistParserFactory() { - @Override - public ParsingLoadable.Parser createPlaylistParser() { - return new HlsPlaylistParser(); - } - - @Override - public ParsingLoadable.Parser createPlaylistParser( - HlsMasterPlaylist masterPlaylist) { - return new HlsPlaylistParser(masterPlaylist); - } - }; - /** * Returns a stand-alone playlist parser. Playlists parsed by the returned parser do not inherit * any attributes from other playlists. From 05dcf502e55e94d4288628e40d0b69bea6bab6d3 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Aug 2018 04:28:11 -0700 Subject: [PATCH 05/42] Add missing cases to IntDef switch + fix default locale usage ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209396260 --- .../exoplayer2/audio/ResamplingAudioProcessor.java | 4 ++++ .../upstream/cache/DefaultContentMetadata.java | 9 ++++++--- .../java/com/google/android/exoplayer2/util/Util.java | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java index eac0bffd65..3ae1a393f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java @@ -98,6 +98,8 @@ import java.nio.ByteOrder; break; case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_FLOAT: + case C.ENCODING_PCM_A_LAW: + case C.ENCODING_PCM_MU_LAW: case C.ENCODING_INVALID: case Format.NO_VALUE: default: @@ -134,6 +136,8 @@ import java.nio.ByteOrder; break; case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_FLOAT: + case C.ENCODING_PCM_A_LAW: + case C.ENCODING_PCM_MU_LAW: case C.ENCODING_INVALID: case Format.NO_VALUE: default: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java index cf63bcc4f6..e16ff5483a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java @@ -188,9 +188,12 @@ public final class DefaultContentMetadata implements ContentMetadata { byte[] bytes = getBytes(value); if (bytes.length > MAX_VALUE_LENGTH) { throw new IllegalArgumentException( - String.format( - "The size of %s (%d) is greater than maximum allowed: %d", - name, bytes.length, MAX_VALUE_LENGTH)); + "The size of " + + name + + " (" + + bytes.length + + ") is greater than maximum allowed: " + + MAX_VALUE_LENGTH); } metadata.put(name, bytes); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 160dccbcad..35cfd3d24b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1245,6 +1245,8 @@ public final class Util { case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_FLOAT: return channelCount * 4; + case C.ENCODING_PCM_A_LAW: + case C.ENCODING_PCM_MU_LAW: case C.ENCODING_INVALID: case Format.NO_VALUE: default: @@ -1325,6 +1327,7 @@ public final class Util { case C.USAGE_NOTIFICATION_EVENT: return C.STREAM_TYPE_NOTIFICATION; case C.USAGE_ASSISTANCE_ACCESSIBILITY: + case C.USAGE_ASSISTANT: case C.USAGE_UNKNOWN: default: return C.STREAM_TYPE_DEFAULT; From 5c2dd9ca42839d36430f0868c5d297d0a91f2e1e Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 20 Aug 2018 07:13:26 -0700 Subject: [PATCH 06/42] Move all tests to JUnit 4 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209412403 --- extensions/flac/build.gradle | 2 + .../flac/src/androidTest/AndroidManifest.xml | 2 +- .../exoplayer2/ext/flac/FlacPlaybackTest.java | 25 ++++--- extensions/opus/build.gradle | 2 + .../opus/src/androidTest/AndroidManifest.xml | 2 +- .../exoplayer2/ext/opus/OpusPlaybackTest.java | 25 ++++--- extensions/vp9/build.gradle | 2 + .../vp9/src/androidTest/AndroidManifest.xml | 2 +- .../exoplayer2/ext/vp9/VpxPlaybackTest.java | 27 +++++--- library/core/build.gradle | 12 ++-- .../core/src/androidTest/AndroidManifest.xml | 2 +- playbacktests/build.gradle | 3 + .../src/androidTest/AndroidManifest.xml | 2 +- .../gts/CommonEncryptionDrmTest.java | 50 ++++++++------ .../playbacktests/gts/DashDownloadTest.java | 48 +++++++------ .../playbacktests/gts/DashStreamingTest.java | 69 ++++++++++++++----- .../gts/DashWidevineOfflineTest.java | 53 ++++++++------ .../gts/EnumerateDecodersTest.java | 16 +++-- 18 files changed, 215 insertions(+), 129 deletions(-) diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 98b81d911a..e5261902c6 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -27,6 +27,7 @@ android { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion consumerProguardFiles 'proguard-rules.txt' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } sourceSets.main { @@ -38,6 +39,7 @@ android { dependencies { implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation project(modulePrefix + 'library-core') + androidTestImplementation 'androidx.test:runner:' + testRunnerVersion androidTestImplementation project(modulePrefix + 'testutils') testImplementation project(modulePrefix + 'testutils-robolectric') } diff --git a/extensions/flac/src/androidTest/AndroidManifest.xml b/extensions/flac/src/androidTest/AndroidManifest.xml index 4e3925d8e3..cfc90117ac 100644 --- a/extensions/flac/src/androidTest/AndroidManifest.xml +++ b/extensions/flac/src/androidTest/AndroidManifest.xml @@ -26,6 +26,6 @@ + android:name="androidx.test.runner.AndroidJUnitRunner"/> diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 07b7a0ccdb..a78556b6c7 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.ext.flac; +import static androidx.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.fail; + import android.content.Context; import android.net.Uri; import android.os.Looper; -import android.test.InstrumentationTestCase; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -29,29 +32,31 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; -/** - * Playback tests using {@link LibflacAudioRenderer}. - */ -public class FlacPlaybackTest extends InstrumentationTestCase { +/** Playback tests using {@link LibflacAudioRenderer}. */ +@RunWith(AndroidJUnit4.class) +public class FlacPlaybackTest { private static final String BEAR_FLAC_URI = "asset:///bear-flac.mka"; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { if (!FlacLibrary.isAvailable()) { fail("Flac library not available."); } } + @Test public void testBasicPlayback() throws ExoPlaybackException { playUri(BEAR_FLAC_URI); } private void playUri(String uri) throws ExoPlaybackException { - TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), - getInstrumentation().getContext()); + TestPlaybackRunnable testPlaybackRunnable = + new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); try { diff --git a/extensions/opus/build.gradle b/extensions/opus/build.gradle index dc530d05aa..cb12442de8 100644 --- a/extensions/opus/build.gradle +++ b/extensions/opus/build.gradle @@ -27,6 +27,7 @@ android { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion consumerProguardFiles 'proguard-rules.txt' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } sourceSets.main { @@ -37,6 +38,7 @@ android { dependencies { implementation project(modulePrefix + 'library-core') + androidTestImplementation 'androidx.test:runner:' + testRunnerVersion } ext { diff --git a/extensions/opus/src/androidTest/AndroidManifest.xml b/extensions/opus/src/androidTest/AndroidManifest.xml index 9e7f05051e..5ba0f3c0f4 100644 --- a/extensions/opus/src/androidTest/AndroidManifest.xml +++ b/extensions/opus/src/androidTest/AndroidManifest.xml @@ -26,6 +26,6 @@ + android:name="androidx.test.runner.AndroidJUnitRunner"/> diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 8e3a213af1..cad63f84df 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.ext.opus; +import static androidx.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.fail; + import android.content.Context; import android.net.Uri; import android.os.Looper; -import android.test.InstrumentationTestCase; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -29,29 +32,31 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; -/** - * Playback tests using {@link LibopusAudioRenderer}. - */ -public class OpusPlaybackTest extends InstrumentationTestCase { +/** Playback tests using {@link LibopusAudioRenderer}. */ +@RunWith(AndroidJUnit4.class) +public class OpusPlaybackTest { private static final String BEAR_OPUS_URI = "asset:///bear-opus.webm"; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { if (!OpusLibrary.isAvailable()) { fail("Opus library not available."); } } + @Test public void testBasicPlayback() throws ExoPlaybackException { playUri(BEAR_OPUS_URI); } private void playUri(String uri) throws ExoPlaybackException { - TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), - getInstrumentation().getContext()); + TestPlaybackRunnable testPlaybackRunnable = + new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); try { diff --git a/extensions/vp9/build.gradle b/extensions/vp9/build.gradle index 3fb627fd77..96c58d7a57 100644 --- a/extensions/vp9/build.gradle +++ b/extensions/vp9/build.gradle @@ -27,6 +27,7 @@ android { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion consumerProguardFiles 'proguard-rules.txt' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } sourceSets.main { @@ -38,6 +39,7 @@ android { dependencies { implementation project(modulePrefix + 'library-core') implementation 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestImplementation 'androidx.test:runner:' + testRunnerVersion androidTestImplementation 'com.google.truth:truth:' + truthVersion } diff --git a/extensions/vp9/src/androidTest/AndroidManifest.xml b/extensions/vp9/src/androidTest/AndroidManifest.xml index c7ed3d7fb2..214427c4f0 100644 --- a/extensions/vp9/src/androidTest/AndroidManifest.xml +++ b/extensions/vp9/src/androidTest/AndroidManifest.xml @@ -26,6 +26,6 @@ + android:name="androidx.test.runner.AndroidJUnitRunner"/> diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index bab7cb6fd7..119347ccbf 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -15,13 +15,15 @@ */ package com.google.android.exoplayer2.ext.vp9; +import static androidx.test.InstrumentationRegistry.getContext; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import android.content.Context; import android.net.Uri; import android.os.Looper; -import android.test.InstrumentationTestCase; import android.util.Log; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -32,11 +34,13 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; -/** - * Playback tests using {@link LibvpxVideoRenderer}. - */ -public class VpxPlaybackTest extends InstrumentationTestCase { +/** Playback tests using {@link LibvpxVideoRenderer}. */ +@RunWith(AndroidJUnit4.class) +public class VpxPlaybackTest { private static final String BEAR_URI = "asset:///bear-vp9.webm"; private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///bear-vp9-odd-dimensions.webm"; @@ -45,22 +49,24 @@ public class VpxPlaybackTest extends InstrumentationTestCase { private static final String TAG = "VpxPlaybackTest"; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { if (!VpxLibrary.isAvailable()) { fail("Vpx library not available."); } } + @Test public void testBasicPlayback() throws ExoPlaybackException { playUri(BEAR_URI); } + @Test public void testOddDimensionsPlayback() throws ExoPlaybackException { playUri(BEAR_ODD_DIMENSIONS_URI); } + @Test public void test10BitProfile2Playback() throws ExoPlaybackException { if (VpxLibrary.isHighBitDepthSupported()) { Log.d(TAG, "High Bit Depth supported."); @@ -70,6 +76,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { Log.d(TAG, "High Bit Depth not supported."); } + @Test public void testInvalidBitstream() { try { playUri(INVALID_BITSTREAM_URI); @@ -81,8 +88,8 @@ public class VpxPlaybackTest extends InstrumentationTestCase { } private void playUri(String uri) throws ExoPlaybackException { - TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), - getInstrumentation().getContext()); + TestPlaybackRunnable testPlaybackRunnable = + new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); try { diff --git a/library/core/build.gradle b/library/core/build.gradle index 947972392f..606033fdea 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -28,7 +28,7 @@ android { targetSdkVersion project.ext.targetSdkVersion consumerProguardFiles 'proguard-rules.txt' - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' // The following argument makes the Android Test Orchestrator run its // "pm clear" command after each test invocation. This command ensures @@ -39,11 +39,11 @@ android { // Workaround to prevent circular dependency on project :testutils. sourceSets { androidTest { - java.srcDirs += "../../testutils/src/main/java/" + java.srcDirs += '../../testutils/src/main/java/' } test { - java.srcDirs += "../../testutils/src/main/java/" - java.srcDirs += "../../testutils_robolectric/src/main/java/" + java.srcDirs += '../../testutils/src/main/java/' + java.srcDirs += '../../testutils_robolectric/src/main/java/' } } @@ -60,12 +60,12 @@ dependencies { implementation 'com.android.support:support-annotations:' + supportLibraryVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion + androidTestImplementation 'androidx.test:runner:' + testRunnerVersion + androidTestImplementation 'com.google.auto.value:auto-value-annotations:' + autoValueVersion androidTestImplementation 'com.google.dexmaker:dexmaker:' + dexmakerVersion androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion androidTestImplementation 'com.google.truth:truth:' + truthVersion androidTestImplementation 'org.mockito:mockito-core:' + mockitoVersion - androidTestImplementation 'androidx.test:runner:' + testRunnerVersion - androidTestImplementation 'com.google.auto.value:auto-value-annotations:' + autoValueVersion androidTestAnnotationProcessor 'com.google.auto.value:auto-value:' + autoValueVersion testImplementation 'com.google.truth:truth:' + truthVersion testImplementation 'junit:junit:' + junitVersion diff --git a/library/core/src/androidTest/AndroidManifest.xml b/library/core/src/androidTest/AndroidManifest.xml index 1aa47c10f6..d9104b1077 100644 --- a/library/core/src/androidTest/AndroidManifest.xml +++ b/library/core/src/androidTest/AndroidManifest.xml @@ -29,6 +29,6 @@ + android:name="androidx.test.runner.AndroidJUnitRunner"/> diff --git a/playbacktests/build.gradle b/playbacktests/build.gradle index f40d30f331..a06a3160f1 100644 --- a/playbacktests/build.gradle +++ b/playbacktests/build.gradle @@ -26,10 +26,13 @@ android { defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } } dependencies { + androidTestImplementation 'androidx.test:rules:' + testRunnerVersion + androidTestImplementation 'androidx.test:runner:' + testRunnerVersion androidTestImplementation project(modulePrefix + 'library-core') androidTestImplementation project(modulePrefix + 'library-dash') androidTestImplementation project(modulePrefix + 'library-hls') diff --git a/playbacktests/src/androidTest/AndroidManifest.xml b/playbacktests/src/androidTest/AndroidManifest.xml index d4fd0b61f1..d458df55bb 100644 --- a/playbacktests/src/androidTest/AndroidManifest.xml +++ b/playbacktests/src/androidTest/AndroidManifest.xml @@ -34,6 +34,6 @@ + android:name="androidx.test.runner.AndroidJUnitRunner"/> diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java index a4cd35911b..1f8337355b 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java @@ -15,17 +15,24 @@ */ package com.google.android.exoplayer2.playbacktests.gts; -import android.test.ActivityInstrumentationTestCase2; +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.HostActivity; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; -/** - * Test playback of encrypted DASH streams using different CENC scheme types. - */ -public final class CommonEncryptionDrmTest extends ActivityInstrumentationTestCase2 { +/** Test playback of encrypted DASH streams using different CENC scheme types. */ +@RunWith(AndroidJUnit4.class) +public final class CommonEncryptionDrmTest { private static final String TAG = "CencDrmTest"; @@ -44,29 +51,26 @@ public final class CommonEncryptionDrmTest extends ActivityInstrumentationTestCa .seekAndWait(270000).delay(10000).seekAndWait(200000).delay(10000).seekAndWait(732000) .build(); + @Rule public ActivityTestRule testRule = new ActivityTestRule<>(HostActivity.class); + private DashTestRunner testRunner; - public CommonEncryptionDrmTest() { - super(HostActivity.class); + @Before + public void setUp() { + testRunner = + new DashTestRunner(TAG, testRule.getActivity(), getInstrumentation()) + .setWidevineInfo(MimeTypes.VIDEO_H264, false) + .setActionSchedule(ACTION_SCHEDULE_WITH_SEEKS) + .setAudioVideoFormats(ID_AUDIO, IDS_VIDEO) + .setCanIncludeAdditionalVideoFormats(true); } - @Override - protected void setUp() throws Exception { - super.setUp(); - - testRunner = new DashTestRunner(TAG, getActivity(), getInstrumentation()) - .setWidevineInfo(MimeTypes.VIDEO_H264, false) - .setActionSchedule(ACTION_SCHEDULE_WITH_SEEKS) - .setAudioVideoFormats(ID_AUDIO, IDS_VIDEO) - .setCanIncludeAdditionalVideoFormats(true); - } - - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() { testRunner = null; - super.tearDown(); } + @Test public void testCencSchemeTypeV18() { if (Util.SDK_INT < 18) { // Pass. @@ -75,6 +79,7 @@ public final class CommonEncryptionDrmTest extends ActivityInstrumentationTestCa testRunner.setStreamName("test_widevine_h264_scheme_cenc").setManifestUrl(URL_cenc).run(); } + @Test public void testCbc1SchemeTypeV25() { if (Util.SDK_INT < 25) { // cbc1 support was added in API 24, but it is stable from API 25 onwards. @@ -85,6 +90,7 @@ public final class CommonEncryptionDrmTest extends ActivityInstrumentationTestCa testRunner.setStreamName("test_widevine_h264_scheme_cbc1").setManifestUrl(URL_cbc1).run(); } + @Test public void testCbcsSchemeTypeV25() { if (Util.SDK_INT < 25) { // cbcs support was added in API 24, but it is stable from API 25 onwards. @@ -95,8 +101,8 @@ public final class CommonEncryptionDrmTest extends ActivityInstrumentationTestCa testRunner.setStreamName("test_widevine_h264_scheme_cbcs").setManifestUrl(URL_cbcs).run(); } + @Test public void testCensSchemeTypeV25() { // TODO: Implement once content is available. Track [internal: b/31219813]. } - } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java index 79d39096c5..0dd05e7fd3 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java @@ -15,10 +15,12 @@ */ package com.google.android.exoplayer2.playbacktests.gts; +import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertWithMessage; import android.net.Uri; -import android.test.ActivityInstrumentationTestCase2; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.dash.DashUtil; @@ -37,36 +39,38 @@ import com.google.android.exoplayer2.util.Util; import java.io.File; import java.util.ArrayList; import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; -/** - * Tests downloaded DASH playbacks. - */ -public final class DashDownloadTest extends ActivityInstrumentationTestCase2 { +/** Tests downloaded DASH playbacks. */ +@RunWith(AndroidJUnit4.class) +public final class DashDownloadTest { private static final String TAG = "DashDownloadTest"; private static final Uri MANIFEST_URI = Uri.parse(DashTestData.H264_MANIFEST); + @Rule public ActivityTestRule testRule = new ActivityTestRule<>(HostActivity.class); + private DashTestRunner testRunner; private File tempFolder; private SimpleCache cache; private DefaultHttpDataSourceFactory httpDataSourceFactory; private CacheDataSourceFactory offlineDataSourceFactory; - public DashDownloadTest() { - super(HostActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - testRunner = new DashTestRunner(TAG, getActivity(), getInstrumentation()) - .setManifestUrl(DashTestData.H264_MANIFEST) - .setFullPlaybackNoSeeking(true) - .setCanIncludeAdditionalVideoFormats(false) - .setAudioVideoFormats(DashTestData.AAC_AUDIO_REPRESENTATION_ID, - DashTestData.H264_CDD_FIXED); - tempFolder = Util.createTempDirectory(getActivity(), "ExoPlayerTest"); + @Before + public void setUp() throws Exception { + testRunner = + new DashTestRunner(TAG, testRule.getActivity(), getInstrumentation()) + .setManifestUrl(DashTestData.H264_MANIFEST) + .setFullPlaybackNoSeeking(true) + .setCanIncludeAdditionalVideoFormats(false) + .setAudioVideoFormats( + DashTestData.AAC_AUDIO_REPRESENTATION_ID, DashTestData.H264_CDD_FIXED); + tempFolder = Util.createTempDirectory(testRule.getActivity(), "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); httpDataSourceFactory = new DefaultHttpDataSourceFactory("ExoPlayer", null); offlineDataSourceFactory = @@ -74,16 +78,16 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2 { +/** Tests DASH playbacks using {@link ExoPlayer}. */ +@RunWith(AndroidJUnit4.class) +public final class DashStreamingTest { private static final String TAG = "DashStreamingTest"; @@ -78,27 +84,24 @@ public final class DashStreamingTest extends ActivityInstrumentationTestCase2 testRule = new ActivityTestRule<>(HostActivity.class); + private DashTestRunner testRunner; - public DashStreamingTest() { - super(HostActivity.class); + @Before + public void setUp() { + testRunner = new DashTestRunner(TAG, testRule.getActivity(), getInstrumentation()); } - @Override - protected void setUp() throws Exception { - super.setUp(); - testRunner = new DashTestRunner(TAG, getActivity(), getInstrumentation()); - } - - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() { testRunner = null; - super.tearDown(); } // H264 CDD. + @Test public void testH264Fixed() { if (Util.SDK_INT < 16) { // Pass. @@ -113,6 +116,7 @@ public final class DashStreamingTest extends ActivityInstrumentationTestCase2 { +/** Tests Widevine encrypted DASH playbacks using offline keys. */ +@RunWith(AndroidJUnit4.class) +public final class DashWidevineOfflineTest { private static final String TAG = "DashWidevineOfflineTest"; private static final String USER_AGENT = "ExoPlayerPlaybackTests"; @@ -50,21 +57,20 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa private OfflineLicenseHelper offlineLicenseHelper; private byte[] offlineLicenseKeySetId; - public DashWidevineOfflineTest() { - super(HostActivity.class); - } + @Rule public ActivityTestRule testRule = new ActivityTestRule<>(HostActivity.class); - @Override - protected void setUp() throws Exception { - super.setUp(); - testRunner = new DashTestRunner(TAG, getActivity(), getInstrumentation()) - .setStreamName("test_widevine_h264_fixed_offline") - .setManifestUrl(DashTestData.WIDEVINE_H264_MANIFEST) - .setWidevineInfo(MimeTypes.VIDEO_H264, true) - .setFullPlaybackNoSeeking(true) - .setCanIncludeAdditionalVideoFormats(false) - .setAudioVideoFormats(DashTestData.WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, - DashTestData.WIDEVINE_H264_CDD_FIXED); + @Before + public void setUp() throws Exception { + testRunner = + new DashTestRunner(TAG, testRule.getActivity(), getInstrumentation()) + .setStreamName("test_widevine_h264_fixed_offline") + .setManifestUrl(DashTestData.WIDEVINE_H264_MANIFEST) + .setWidevineInfo(MimeTypes.VIDEO_H264, true) + .setFullPlaybackNoSeeking(true) + .setCanIncludeAdditionalVideoFormats(false) + .setAudioVideoFormats( + DashTestData.WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, + DashTestData.WIDEVINE_H264_CDD_FIXED); boolean useL1Widevine = DashTestRunner.isL1WidevineAvailable(MimeTypes.VIDEO_H264); String widevineLicenseUrl = DashTestData.getWidevineLicenseUrl(true, useL1Widevine); @@ -75,8 +81,8 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa } } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { testRunner = null; if (offlineLicenseKeySetId != null) { releaseLicense(); @@ -86,11 +92,11 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa } offlineLicenseHelper = null; httpDataSourceFactory = null; - super.tearDown(); } // Offline license tests + @Test public void testWidevineOfflineLicenseV22() throws Exception { if (Util.SDK_INT < 22) { return; // Pass. @@ -103,6 +109,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa assertThat(offlineLicenseKeySetId).isNotNull(); } + @Test public void testWidevineOfflineReleasedLicenseV22() throws Throwable { if (Util.SDK_INT < 22) { return; // Pass. @@ -129,6 +136,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa } } + @Test public void testWidevineOfflineExpiredLicenseV22() throws Exception { if (Util.SDK_INT < 22) { return; // Pass. @@ -158,6 +166,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa testRunner.run(); } + @Test public void testWidevineOfflineLicenseExpiresOnPauseV22() throws Exception { if (Util.SDK_INT < 22) { return; // Pass. diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java index 5157ab672c..669a5a5a0d 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java @@ -15,11 +15,13 @@ */ package com.google.android.exoplayer2.playbacktests.gts; +import static androidx.test.InstrumentationRegistry.getInstrumentation; + import android.media.MediaCodecInfo.AudioCapabilities; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecInfo.VideoCapabilities; -import android.test.InstrumentationTestCase; +import androidx.test.runner.AndroidJUnit4; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; @@ -29,9 +31,13 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; /** Tests enumeration of decoders using {@link MediaCodecUtil}. */ -public class EnumerateDecodersTest extends InstrumentationTestCase { +@RunWith(AndroidJUnit4.class) +public class EnumerateDecodersTest { private static final String TAG = "EnumerateDecodersTest"; @@ -40,14 +46,14 @@ public class EnumerateDecodersTest extends InstrumentationTestCase { private MetricsLogger metricsLogger; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { metricsLogger = MetricsLogger.Factory.createDefault( getInstrumentation(), TAG, REPORT_NAME, REPORT_OBJECT_NAME); } + @Test public void testEnumerateDecoders() throws Exception { enumerateDecoders(MimeTypes.VIDEO_H263); enumerateDecoders(MimeTypes.VIDEO_H264); From e56a9d1bb5384c33a6136b369c98d38b9f997722 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 20 Aug 2018 07:32:32 -0700 Subject: [PATCH 07/42] Add missing API level checks in EnumerateDecodersTest ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209414373 --- .../gts/EnumerateDecodersTest.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java index 669a5a5a0d..b9c513fe72 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java @@ -88,33 +88,44 @@ public class EnumerateDecodersTest { } private void enumerateDecoders(String mimeType) throws DecoderQueryException { - logDecoderInfos(MediaCodecUtil.getDecoderInfos(mimeType, /* secure= */ false)); - logDecoderInfos(MediaCodecUtil.getDecoderInfos(mimeType, /* secure= */ true)); + logDecoderInfos(mimeType, /* secure= */ false); + logDecoderInfos(mimeType, /* secure= */ true); } - private void logDecoderInfos(List mediaCodecInfos) { + private void logDecoderInfos(String mimeType, boolean secure) throws DecoderQueryException { + List mediaCodecInfos = MediaCodecUtil.getDecoderInfos(mimeType, secure); for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) { CodecCapabilities capabilities = Assertions.checkNotNull(mediaCodecInfo.capabilities); metricsLogger.logMetric( - "capabilities_" + mediaCodecInfo.name, codecCapabilitiesToString(capabilities)); + "capabilities_" + mediaCodecInfo.name, codecCapabilitiesToString(mimeType, capabilities)); } } - private static String codecCapabilitiesToString(CodecCapabilities codecCapabilities) { - String mimeType = codecCapabilities.getMimeType(); - boolean isVideo = MimeTypes.isVideo(mimeType); - boolean isAudio = MimeTypes.isAudio(mimeType); + private static String codecCapabilitiesToString( + String requestedMimeType, CodecCapabilities codecCapabilities) { + boolean isVideo = MimeTypes.isVideo(requestedMimeType); + boolean isAudio = MimeTypes.isAudio(requestedMimeType); StringBuilder result = new StringBuilder(); - result.append("[mimeType=").append(mimeType).append(", profileLevels="); - profileLevelsToString(codecCapabilities.profileLevels, result); - result.append(", maxSupportedInstances=").append(codecCapabilities.getMaxSupportedInstances()); - if (isVideo) { - result.append(", videoCapabilities="); - videoCapabilitiesToString(codecCapabilities.getVideoCapabilities(), result); - result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats)); - } else if (isAudio) { - result.append(", audioCapabilities="); - audioCapabilitiesToString(codecCapabilities.getAudioCapabilities(), result); + result.append("[requestedMimeType=").append(requestedMimeType); + if (Util.SDK_INT >= 21) { + result.append(", mimeType=").append(codecCapabilities.getMimeType()); + } + result.append(", profileLevels="); + appendProfileLevels(codecCapabilities.profileLevels, result); + if (Util.SDK_INT >= 23) { + result + .append(", maxSupportedInstances=") + .append(codecCapabilities.getMaxSupportedInstances()); + } + if (Util.SDK_INT >= 21) { + if (isVideo) { + result.append(", videoCapabilities="); + appendVideoCapabilities(codecCapabilities.getVideoCapabilities(), result); + result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats)); + } else if (isAudio) { + result.append(", audioCapabilities="); + appendAudioCapabilities(codecCapabilities.getAudioCapabilities(), result); + } } if (Util.SDK_INT >= 19 && isVideo @@ -140,7 +151,7 @@ public class EnumerateDecodersTest { return result.toString(); } - private static void audioCapabilitiesToString( + private static void appendAudioCapabilities( AudioCapabilities audioCapabilities, StringBuilder result) { result .append("[bitrateRange=") @@ -152,7 +163,7 @@ public class EnumerateDecodersTest { .append(']'); } - private static void videoCapabilitiesToString( + private static void appendVideoCapabilities( VideoCapabilities videoCapabilities, StringBuilder result) { result .append("[bitrateRange=") @@ -170,8 +181,7 @@ public class EnumerateDecodersTest { .append(']'); } - private static void profileLevelsToString( - CodecProfileLevel[] profileLevels, StringBuilder result) { + private static void appendProfileLevels(CodecProfileLevel[] profileLevels, StringBuilder result) { result.append('['); int count = profileLevels.length; for (int i = 0; i < count; i++) { From 2cd7d7102baafc2f08d51e095050860904163ddc Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 20 Aug 2018 07:55:19 -0700 Subject: [PATCH 08/42] Add DummyExtractorOutput ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209417004 --- .../extractor/DummyExtractorOutput.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyExtractorOutput.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyExtractorOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyExtractorOutput.java new file mode 100644 index 0000000000..f199493500 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyExtractorOutput.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor; + +/** A dummy {@link ExtractorOutput} implementation. */ +public final class DummyExtractorOutput implements ExtractorOutput { + + @Override + public TrackOutput track(int id, int type) { + return new DummyTrackOutput(); + } + + @Override + public void endTracks() { + // Do nothing. + } + + @Override + public void seekMap(SeekMap seekMap) { + // Do nothing. + } +} From d51b98dd1f6290504b4be9d86d71e70ebe217513 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 20 Aug 2018 09:24:58 -0700 Subject: [PATCH 09/42] Replace period index with uid in MediaPeriodId. The MediaPeriodId with index is only properly defined together with a timeline containing the index. Changing it to the period uid allows to use the MediaPeriodId independent of the corresponding timeline. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209430257 --- .../android/exoplayer2/ExoPlayerImpl.java | 17 +- .../exoplayer2/ExoPlayerImplInternal.java | 106 ++-- .../android/exoplayer2/MediaPeriodHolder.java | 4 +- .../android/exoplayer2/MediaPeriodInfo.java | 14 - .../android/exoplayer2/MediaPeriodQueue.java | 156 +++--- .../android/exoplayer2/PlaybackInfo.java | 20 +- .../google/android/exoplayer2/Timeline.java | 22 +- .../analytics/AnalyticsCollector.java | 17 +- .../source/AbstractConcatenatedTimeline.java | 45 +- .../source/ConcatenatingMediaSource.java | 47 +- .../source/ExtractorMediaSource.java | 1 - .../exoplayer2/source/LoopingMediaSource.java | 12 +- .../exoplayer2/source/MediaSource.java | 49 +- .../exoplayer2/source/MergingMediaSource.java | 14 +- .../source/SingleSampleMediaSource.java | 1 - .../exoplayer2/source/ads/AdsMediaSource.java | 41 +- .../android/exoplayer2/util/EventLogger.java | 3 +- .../android/exoplayer2/ExoPlayerTest.java | 43 +- .../analytics/AnalyticsCollectorTest.java | 464 ++++++++++-------- .../source/ClippingMediaSourceTest.java | 15 +- .../source/ConcatenatingMediaSourceTest.java | 94 ++-- .../source/SinglePeriodTimelineTest.java | 12 +- .../source/dash/DashMediaPeriod.java | 9 +- .../source/dash/DashMediaSource.java | 2 +- .../exoplayer2/source/hls/HlsMediaSource.java | 1 - .../source/smoothstreaming/SsMediaSource.java | 1 - .../testutil/FakeAdaptiveMediaSource.java | 2 +- .../exoplayer2/testutil/FakeMediaSource.java | 5 +- .../testutil/MediaSourceTestRunner.java | 12 +- 29 files changed, 649 insertions(+), 580 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 648168816f..7149cf5cc2 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 @@ -327,10 +327,10 @@ import java.util.concurrent.CopyOnWriteArraySet; } else { long windowPositionUs = positionMs == C.TIME_UNSET ? timeline.getWindow(windowIndex, window).getDefaultPositionUs() : C.msToUs(positionMs); - Pair periodIndexAndPosition = + Pair periodUidAndPosition = timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); maskingWindowPositionMs = C.usToMs(windowPositionUs); - maskingPeriodIndex = periodIndexAndPosition.first; + maskingPeriodIndex = timeline.getIndexOfPeriod(periodUidAndPosition.first); } internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); for (Player.EventListener listener : listeners) { @@ -464,7 +464,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (shouldMaskPosition()) { return maskingPeriodIndex; } else { - return playbackInfo.periodId.periodIndex; + return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid); } } @@ -473,7 +473,8 @@ import java.util.concurrent.CopyOnWriteArraySet; if (shouldMaskPosition()) { return maskingWindowIndex; } else { - return playbackInfo.timeline.getPeriod(playbackInfo.periodId.periodIndex, period).windowIndex; + return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period) + .windowIndex; } } @@ -499,7 +500,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (isPlayingAd()) { MediaPeriodId periodId = playbackInfo.periodId; - timeline.getPeriod(periodId.periodIndex, period); + timeline.getPeriodByUid(periodId.periodUid, period); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); return C.usToMs(adDurationUs); } else { @@ -572,7 +573,7 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public long getContentPosition() { if (isPlayingAd()) { - playbackInfo.timeline.getPeriod(playbackInfo.periodId.periodIndex, period); + playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs); } else { return getCurrentPosition(); @@ -591,7 +592,7 @@ import java.util.concurrent.CopyOnWriteArraySet; long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.isAd()) { Timeline.Period loadingPeriod = - playbackInfo.timeline.getPeriod(playbackInfo.loadingMediaPeriodId.periodIndex, period); + playbackInfo.timeline.getPeriodByUid(playbackInfo.loadingMediaPeriodId.periodUid, period); contentBufferedPositionUs = loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex); if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) { @@ -761,7 +762,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) { long positionMs = C.usToMs(positionUs); - playbackInfo.timeline.getPeriod(periodId.periodIndex, period); + playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); positionMs += period.getPositionInWindowMs(); return positionMs; } 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 0a5b4b72d4..f2aeb7321d 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 @@ -594,7 +594,7 @@ import java.util.Collections; long periodPositionUs; long contentPositionUs; boolean seekPositionAdjusted; - Pair resolvedSeekPosition = + Pair resolvedSeekPosition = resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true); if (resolvedSeekPosition == null) { // The seek position was valid for the timeline that it was performed into, but the @@ -605,9 +605,9 @@ import java.util.Collections; seekPositionAdjusted = true; } else { // Update the resolved seek position to take ads into account. - int periodIndex = resolvedSeekPosition.first; + Object periodUid = resolvedSeekPosition.first; contentPositionUs = resolvedSeekPosition.second; - periodId = queue.resolveMediaPeriodIdForAds(periodIndex, contentPositionUs); + periodId = queue.resolveMediaPeriodIdForAds(periodUid, contentPositionUs); if (periodId.isAd()) { periodPositionUs = 0; seekPositionAdjusted = true; @@ -760,7 +760,7 @@ import java.util.Collections; int firstPeriodIndex = timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) .firstPeriodIndex; - return new MediaPeriodId(firstPeriodIndex); + return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex)); } private void resetInternal( @@ -889,7 +889,7 @@ import java.util.Collections; private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) { if (pendingMessageInfo.resolvedPeriodUid == null) { // Position is still unresolved. Try to find window in current timeline. - Pair periodPosition = + Pair periodPosition = resolveSeekPosition( new SeekPosition( pendingMessageInfo.message.getTimeline(), @@ -900,9 +900,9 @@ import java.util.Collections; return false; } pendingMessageInfo.setResolvedPosition( - periodPosition.first, + playbackInfo.timeline.getIndexOfPeriod(periodPosition.first), periodPosition.second, - playbackInfo.timeline.getUidOfPeriod(periodPosition.first)); + periodPosition.first); } else { // Position has been resolved for a previous timeline. Try to find the updated period index. int index = playbackInfo.timeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid); @@ -925,7 +925,8 @@ import java.util.Collections; oldPeriodPositionUs--; } // Correct next index if necessary (e.g. after seeking, timeline changes, or new messages) - int currentPeriodIndex = playbackInfo.periodId.periodIndex; + int currentPeriodIndex = + playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid); PendingMessageInfo previousInfo = nextPendingMessageIndex > 0 ? pendingMessages.get(nextPendingMessageIndex - 1) : null; while (previousInfo != null @@ -1153,7 +1154,7 @@ import java.util.Collections; playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount); pendingPrepareCount = 0; if (pendingInitialSeekPosition != null) { - Pair periodPosition; + Pair periodPosition; try { periodPosition = resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); @@ -1168,9 +1169,9 @@ import java.util.Collections; // timeline has changed and a suitable seek position could not be resolved in the new one. handleSourceInfoRefreshEndedPlayback(); } else { - int periodIndex = periodPosition.first; + Object periodUid = periodPosition.first; long positionUs = periodPosition.second; - MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, positionUs); + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs); playbackInfo = playbackInfo.fromNewPosition( periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); @@ -1179,11 +1180,12 @@ import java.util.Collections; if (timeline.isEmpty()) { handleSourceInfoRefreshEndedPlayback(); } else { - Pair defaultPosition = getPeriodPosition(timeline, - timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); - int periodIndex = defaultPosition.first; + Pair defaultPosition = + getPeriodPosition( + timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); + Object periodUid = defaultPosition.first; long startPositionUs = defaultPosition.second; - MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, startPositionUs); + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = playbackInfo.fromNewPosition( periodId, @@ -1197,12 +1199,12 @@ import java.util.Collections; if (oldTimeline.isEmpty()) { // If the old timeline is empty, the period queue is also empty. if (!timeline.isEmpty()) { - Pair defaultPosition = + Pair defaultPosition = getPeriodPosition( timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); - int periodIndex = defaultPosition.first; + Object periodUid = defaultPosition.first; long startPositionUs = defaultPosition.second; - MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, startPositionUs); + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = playbackInfo.fromNewPosition( periodId, @@ -1212,37 +1214,32 @@ import java.util.Collections; return; } MediaPeriodHolder periodHolder = queue.getFrontPeriod(); - int playingPeriodIndex = playbackInfo.periodId.periodIndex; long contentPositionUs = playbackInfo.contentPositionUs; Object playingPeriodUid = - periodHolder == null ? oldTimeline.getUidOfPeriod(playingPeriodIndex) : periodHolder.uid; + periodHolder == null ? playbackInfo.periodId.periodUid : periodHolder.uid; int periodIndex = timeline.getIndexOfPeriod(playingPeriodUid); if (periodIndex == C.INDEX_UNSET) { // We didn't find the current period in the new timeline. Attempt to resolve a subsequent // period whose window we can restart from. - int newPeriodIndex = resolveSubsequentPeriod(playingPeriodIndex, oldTimeline, timeline); - if (newPeriodIndex == C.INDEX_UNSET) { + Object newPeriodUid = resolveSubsequentPeriod(playingPeriodUid, oldTimeline, timeline); + if (newPeriodUid == null) { // We failed to resolve a suitable restart position. handleSourceInfoRefreshEndedPlayback(); return; } // We resolved a subsequent period. Seek to the default position in the corresponding window. - Pair defaultPosition = getPeriodPosition(timeline, - timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); - newPeriodIndex = defaultPosition.first; + Pair defaultPosition = + getPeriodPosition( + timeline, timeline.getPeriodByUid(newPeriodUid, period).windowIndex, C.TIME_UNSET); + newPeriodUid = defaultPosition.first; contentPositionUs = defaultPosition.second; - MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodIndex, contentPositionUs); + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodUid, contentPositionUs); if (periodHolder != null) { - // Clear the index of each holder that doesn't contain the default position. If a holder - // contains the default position then update its index so it can be re-used when seeking. - Object newPeriodUid = timeline.getUidOfPeriod(newPeriodIndex); - periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET); + // Update the new playing media period info if it already exists. while (periodHolder.next != null) { periodHolder = periodHolder.next; - if (periodHolder.uid.equals(newPeriodUid)) { - periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info, newPeriodIndex); - } else { - periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET); + if (periodHolder.info.id.equals(periodId)) { + periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info); } } } @@ -1252,14 +1249,10 @@ import java.util.Collections; return; } - // The current period is in the new timeline. Update the playback info. - if (periodIndex != playingPeriodIndex) { - playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex); - } - MediaPeriodId playingPeriodId = playbackInfo.periodId; if (playingPeriodId.isAd()) { - MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, contentPositionUs); + MediaPeriodId periodId = + queue.resolveMediaPeriodIdForAds(playingPeriodUid, contentPositionUs); if (!periodId.equals(playingPeriodId)) { // The previously playing ad should no longer be played, so skip it. long seekPositionUs = @@ -1284,16 +1277,17 @@ import java.util.Collections; /** * Given a period index into an old timeline, finds the first subsequent period that also exists - * in a new timeline. The index of this period in the new timeline is returned. + * in a new timeline. The uid of this period in the new timeline is returned. * - * @param oldPeriodIndex The index of the period in the old timeline. + * @param oldPeriodUid The index of the period in the old timeline. * @param oldTimeline The old timeline. * @param newTimeline The new timeline. - * @return The index in the new timeline of the first subsequent period, or {@link C#INDEX_UNSET} - * if no such period was found. + * @return The uid in the new timeline of the first subsequent period, or null if no such period + * was found. */ - private int resolveSubsequentPeriod( - int oldPeriodIndex, Timeline oldTimeline, Timeline newTimeline) { + private @Nullable Object resolveSubsequentPeriod( + Object oldPeriodUid, Timeline oldTimeline, Timeline newTimeline) { + int oldPeriodIndex = oldTimeline.getIndexOfPeriod(oldPeriodUid); int newPeriodIndex = C.INDEX_UNSET; int maxIterations = oldTimeline.getPeriodCount(); for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) { @@ -1305,11 +1299,11 @@ import java.util.Collections; } newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getUidOfPeriod(oldPeriodIndex)); } - return newPeriodIndex; + return newPeriodIndex == C.INDEX_UNSET ? null : newTimeline.getUidOfPeriod(newPeriodIndex); } /** - * Converts a {@link SeekPosition} into the corresponding (periodIndex, periodPositionUs) for the + * Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the * internal timeline. * * @param seekPosition The position to resolve. @@ -1319,7 +1313,7 @@ import java.util.Collections; * @throws IllegalSeekPositionException If the window index of the seek position is outside the * bounds of the timeline. */ - private Pair resolveSeekPosition( + private Pair resolveSeekPosition( SeekPosition seekPosition, boolean trySubsequentPeriods) { Timeline timeline = playbackInfo.timeline; Timeline seekTimeline = seekPosition.timeline; @@ -1333,7 +1327,7 @@ import java.util.Collections; seekTimeline = timeline; } // Map the SeekPosition to a position in the corresponding timeline. - Pair periodPosition; + Pair periodPosition; try { periodPosition = seekTimeline.getPeriodPosition(window, period, seekPosition.windowIndex, seekPosition.windowPositionUs); @@ -1347,15 +1341,15 @@ import java.util.Collections; return periodPosition; } // Attempt to find the mapped period in the internal timeline. - int periodIndex = timeline.getIndexOfPeriod(seekTimeline.getUidOfPeriod(periodPosition.first)); + int periodIndex = timeline.getIndexOfPeriod(periodPosition.first); if (periodIndex != C.INDEX_UNSET) { // We successfully located the period in the internal timeline. - return Pair.create(periodIndex, periodPosition.second); + return periodPosition; } if (trySubsequentPeriods) { // Try and find a subsequent period from the seek timeline in the internal timeline. - periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); - if (periodIndex != C.INDEX_UNSET) { + Object periodUid = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); + if (periodUid != null) { // We found one. Map the SeekPosition onto the corresponding default position. return getPeriodPosition( timeline, timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); @@ -1369,7 +1363,7 @@ import java.util.Collections; * Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the * current timeline. */ - private Pair getPeriodPosition( + private Pair getPeriodPosition( Timeline timeline, int windowIndex, long windowPositionUs) { return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); } @@ -1506,14 +1500,12 @@ import java.util.Collections; if (info == null) { mediaSource.maybeThrowSourceInfoRefreshError(); } else { - Object uid = playbackInfo.timeline.getUidOfPeriod(info.id.periodIndex); MediaPeriod mediaPeriod = queue.enqueueNextMediaPeriod( rendererCapabilities, trackSelector, loadControl.getAllocator(), mediaSource, - uid, info); mediaPeriod.prepare(this, info.startPositionUs); setIsLoading(true); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index a74a2ac1ca..e42dd03dbe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -62,7 +62,6 @@ import com.google.android.exoplayer2.util.Assertions; * @param trackSelector The track selector. * @param allocator The allocator. * @param mediaSource The media source that produced the media period. - * @param uid The unique identifier for the containing timeline period. * @param info Information used to identify this media period in its timeline period. */ public MediaPeriodHolder( @@ -71,13 +70,12 @@ import com.google.android.exoplayer2.util.Assertions; TrackSelector trackSelector, Allocator allocator, MediaSource mediaSource, - Object uid, MediaPeriodInfo info) { this.rendererCapabilities = rendererCapabilities; this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs; this.trackSelector = trackSelector; this.mediaSource = mediaSource; - this.uid = Assertions.checkNotNull(uid); + this.uid = Assertions.checkNotNull(info.id.periodUid); this.info = info; sampleStreams = new SampleStream[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index b887e8222e..ba19b54c3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -62,20 +62,6 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; this.isFinal = isFinal; } - /** - * Returns a copy of this instance with the period identifier's period index set to the specified - * value. - */ - public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) { - return new MediaPeriodInfo( - id.copyWithPeriodIndex(periodIndex), - startPositionUs, - contentPositionUs, - durationUs, - isLastInTimelinePeriod, - isFinal); - } - /** Returns a copy of this instance with the start position set to the specified value. */ public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) { return new MediaPeriodInfo( 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 e9be2d985e..2edf7bb8c6 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,7 +30,6 @@ import com.google.android.exoplayer2.util.Assertions; * loading media period at the end of the queue, with methods for controlling loading and updating * the queue. Also has a reference to the media period currently being read. */ -@SuppressWarnings("UngroupedOverloads") /* package */ final class MediaPeriodQueue { /** @@ -135,7 +134,6 @@ import com.google.android.exoplayer2.util.Assertions; * @param trackSelector The track selector. * @param allocator The allocator. * @param mediaSource The media source that produced the media period. - * @param uid The unique identifier for the containing timeline period. * @param info Information used to identify this media period in its timeline period. */ public MediaPeriod enqueueNextMediaPeriod( @@ -143,7 +141,6 @@ import com.google.android.exoplayer2.util.Assertions; TrackSelector trackSelector, Allocator allocator, MediaSource mediaSource, - Object uid, MediaPeriodInfo info) { long rendererPositionOffsetUs = loading == null @@ -156,7 +153,6 @@ import com.google.android.exoplayer2.util.Assertions; trackSelector, allocator, mediaSource, - uid, info); if (loading != null) { Assertions.checkState(hasPlayingPeriod()); @@ -304,14 +300,14 @@ import com.google.android.exoplayer2.util.Assertions; // TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline // is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be // handled here. - int periodIndex = playingPeriodId.periodIndex; + int periodIndex = timeline.getIndexOfPeriod(playingPeriodId.periodUid); // The front period is either playing now, or is being loaded and will become the playing // period. MediaPeriodHolder previousPeriodHolder = null; MediaPeriodHolder periodHolder = getFrontPeriod(); while (periodHolder != null) { if (previousPeriodHolder == null) { - periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); + periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info); } else { // Check this period holder still follows the previous one, based on the new timeline. if (periodIndex == C.INDEX_UNSET @@ -325,8 +321,8 @@ import com.google.android.exoplayer2.util.Assertions; // We've loaded a next media period that is not in the new timeline. return !removeAfter(previousPeriodHolder); } - // Update the period index. - periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); + // Update the period holder. + periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info); // Check the media period information matches the new timeline. if (!canKeepMediaPeriodHolder(periodHolder, periodInfo)) { return !removeAfter(previousPeriodHolder); @@ -348,16 +344,29 @@ import com.google.android.exoplayer2.util.Assertions; /** * Returns new media period info based on specified {@code mediaPeriodInfo} but taking into - * account the current timeline, and with the period index updated to {@code newPeriodIndex}. + * account the current timeline. This method must only be called if the period is still part of + * the current timeline. * - * @param mediaPeriodInfo Media period info for a media period based on an old timeline. - * @param newPeriodIndex The new period index in the new timeline for the existing media period. + * @param info Media period info for a media period based on an old timeline. * @return The updated media period info for the current timeline. */ - public MediaPeriodInfo getUpdatedMediaPeriodInfo( - MediaPeriodInfo mediaPeriodInfo, int newPeriodIndex) { - return getUpdatedMediaPeriodInfo( - mediaPeriodInfo, mediaPeriodInfo.id.copyWithPeriodIndex(newPeriodIndex)); + public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info) { + boolean isLastInPeriod = isLastInPeriod(info.id); + boolean isLastInTimeline = isLastInTimeline(info.id, isLastInPeriod); + timeline.getPeriodByUid(info.id.periodUid, period); + long durationUs = + info.id.isAd() + ? period.getAdDurationUs(info.id.adGroupIndex, info.id.adIndexInAdGroup) + : (info.id.endPositionUs == C.TIME_END_OF_SOURCE + ? period.getDurationUs() + : info.id.endPositionUs); + return new MediaPeriodInfo( + info.id, + info.startPositionUs, + info.contentPositionUs, + durationUs, + isLastInPeriod, + isLastInTimeline); } /** @@ -365,13 +374,13 @@ import com.google.android.exoplayer2.util.Assertions; * played, returning an identifier for an ad group if one needs to be played before the specified * position, or an identifier for a content media period if not. * - * @param periodIndex The index of the timeline period to play. + * @param periodUid The uid of the timeline period to play. * @param positionUs The next content position in the period to play. * @return The identifier for the first media period to play, taking into account unplayed ads. */ - public MediaPeriodId resolveMediaPeriodIdForAds(int periodIndex, long positionUs) { - long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodIndex); - return resolveMediaPeriodIdForAds(periodIndex, positionUs, windowSequenceNumber); + public MediaPeriodId resolveMediaPeriodIdForAds(Object periodUid, long positionUs) { + long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodUid); + return resolveMediaPeriodIdForAds(periodUid, positionUs, windowSequenceNumber); } // Internal methods. @@ -381,15 +390,15 @@ import com.google.android.exoplayer2.util.Assertions; * played, returning an identifier for an ad group if one needs to be played before the specified * position, or an identifier for a content media period if not. * - * @param periodIndex The index of the timeline period to play. + * @param periodUid The uid of the timeline period to play. * @param positionUs The next content position in the period to play. * @param windowSequenceNumber The sequence number of the window in the buffered sequence of * windows this period is part of. * @return The identifier for the first media period to play, taking into account unplayed ads. */ private MediaPeriodId resolveMediaPeriodIdForAds( - int periodIndex, long positionUs, long windowSequenceNumber) { - timeline.getPeriod(periodIndex, period); + Object periodUid, long positionUs, long windowSequenceNumber) { + timeline.getPeriodByUid(periodUid, period); int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); if (adGroupIndex == C.INDEX_UNSET) { int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); @@ -397,24 +406,23 @@ import com.google.android.exoplayer2.util.Assertions; nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE : period.getAdGroupTimeUs(nextAdGroupIndex); - return new MediaPeriodId(periodIndex, windowSequenceNumber, endPositionUs); + return new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); } else { int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); - return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); + return new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); } } /** - * Resolves the specified period index to a corresponding window sequence number. Either by - * reusing the window sequence number of an existing matching media period or by creating a new - * window sequence number. + * Resolves the specified period uid to a corresponding window sequence number. Either by reusing + * the window sequence number of an existing matching media period or by creating a new window + * sequence number. * - * @param periodIndex The index of the timeline period. + * @param periodUid The uid of the timeline period. * @return A window sequence number for a media period created for this timeline period. */ - private long resolvePeriodIndexToWindowSequenceNumber(int periodIndex) { - Object periodUid = timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; - int windowIndex = period.windowIndex; + private long resolvePeriodIndexToWindowSequenceNumber(Object periodUid) { + int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex; if (oldFrontPeriodUid != null) { int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid); if (oldFrontPeriodIndex != C.INDEX_UNSET) { @@ -469,32 +477,32 @@ import com.google.android.exoplayer2.util.Assertions; if (lastValidPeriodHolder == null) { return true; } + int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid); while (true) { int nextPeriodIndex = timeline.getNextPeriodIndex( - lastValidPeriodHolder.info.id.periodIndex, - period, - window, - repeatMode, - shuffleModeEnabled); + currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled); while (lastValidPeriodHolder.next != null && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { lastValidPeriodHolder = lastValidPeriodHolder.next; } - if (nextPeriodIndex == C.INDEX_UNSET - || lastValidPeriodHolder.next == null - || lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) { + + if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null) { + break; + } + int nextPeriodHolderPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.next.uid); + if (nextPeriodHolderPeriodIndex != nextPeriodIndex) { break; } lastValidPeriodHolder = lastValidPeriodHolder.next; + currentPeriodIndex = nextPeriodIndex; } // Release any period holders that don't match the new period order. boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder); // Update the period info for the last holder, as it may now be the last period in the timeline. - lastValidPeriodHolder.info = - getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info, lastValidPeriodHolder.info.id); + lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info); // If renderers may have read from a period that's been removed, it is necessary to restart. return !readingPeriodRemoved || !hasPlayingPeriod(); @@ -525,9 +533,10 @@ import com.google.android.exoplayer2.util.Assertions; // timeline is updated, to avoid repeatedly checking the same timeline. MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; if (mediaPeriodInfo.isLastInTimelinePeriod) { + int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid); int nextPeriodIndex = timeline.getNextPeriodIndex( - mediaPeriodInfo.id.periodIndex, period, window, repeatMode, shuffleModeEnabled); + currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled); if (nextPeriodIndex == C.INDEX_UNSET) { // We can't create a next period yet. return null; @@ -546,7 +555,7 @@ import com.google.android.exoplayer2.util.Assertions; // the buffer, and start buffering from this point. long defaultPositionProjectionUs = mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; - Pair defaultPosition = + Pair defaultPosition = timeline.getPeriodPosition( window, period, @@ -556,7 +565,7 @@ import com.google.android.exoplayer2.util.Assertions; if (defaultPosition == null) { return null; } - nextPeriodIndex = defaultPosition.first; + nextPeriodUid = defaultPosition.first; startPositionUs = defaultPosition.second; if (mediaPeriodHolder.next != null && mediaPeriodHolder.next.uid.equals(nextPeriodUid)) { windowSequenceNumber = mediaPeriodHolder.next.info.id.windowSequenceNumber; @@ -567,12 +576,12 @@ import com.google.android.exoplayer2.util.Assertions; startPositionUs = 0; } MediaPeriodId periodId = - resolveMediaPeriodIdForAds(nextPeriodIndex, startPositionUs, windowSequenceNumber); + resolveMediaPeriodIdForAds(nextPeriodUid, startPositionUs, windowSequenceNumber); return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); } MediaPeriodId currentPeriodId = mediaPeriodInfo.id; - timeline.getPeriod(currentPeriodId.periodIndex, period); + timeline.getPeriodByUid(currentPeriodId.periodUid, period); if (currentPeriodId.isAd()) { int adGroupIndex = currentPeriodId.adGroupIndex; int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex); @@ -586,7 +595,7 @@ import com.google.android.exoplayer2.util.Assertions; return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) ? null : getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, + currentPeriodId.periodUid, adGroupIndex, nextAdIndexInAdGroup, mediaPeriodInfo.contentPositionUs, @@ -594,7 +603,7 @@ import com.google.android.exoplayer2.util.Assertions; } else { // Play content from the ad group position. return getMediaPeriodInfoForContent( - currentPeriodId.periodIndex, + currentPeriodId.periodUid, mediaPeriodInfo.contentPositionUs, currentPeriodId.windowSequenceNumber); } @@ -604,7 +613,7 @@ import com.google.android.exoplayer2.util.Assertions; if (nextAdGroupIndex == C.INDEX_UNSET) { // The next ad group can't be played. Play content from the ad group position instead. return getMediaPeriodInfoForContent( - currentPeriodId.periodIndex, + currentPeriodId.periodUid, mediaPeriodInfo.id.endPositionUs, currentPeriodId.windowSequenceNumber); } @@ -612,7 +621,7 @@ import com.google.android.exoplayer2.util.Assertions; return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) ? null : getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, + currentPeriodId.periodUid, nextAdGroupIndex, adIndexInAdGroup, mediaPeriodInfo.id.endPositionUs, @@ -634,7 +643,7 @@ import com.google.android.exoplayer2.util.Assertions; } long contentDurationUs = period.getDurationUs(); return getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, + currentPeriodId.periodUid, adGroupIndex, adIndexInAdGroup, contentDurationUs, @@ -642,57 +651,37 @@ import com.google.android.exoplayer2.util.Assertions; } } - private MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info, MediaPeriodId newId) { - long startPositionUs = info.startPositionUs; - boolean isLastInPeriod = isLastInPeriod(newId); - boolean isLastInTimeline = isLastInTimeline(newId, isLastInPeriod); - timeline.getPeriod(newId.periodIndex, period); - long durationUs = - newId.isAd() - ? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup) - : (newId.endPositionUs == C.TIME_END_OF_SOURCE - ? period.getDurationUs() - : newId.endPositionUs); - return new MediaPeriodInfo( - newId, - startPositionUs, - info.contentPositionUs, - durationUs, - isLastInPeriod, - isLastInTimeline); - } - private MediaPeriodInfo getMediaPeriodInfo( MediaPeriodId id, long contentPositionUs, long startPositionUs) { - timeline.getPeriod(id.periodIndex, period); + timeline.getPeriodByUid(id.periodUid, period); if (id.isAd()) { if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { return null; } return getMediaPeriodInfoForAd( - id.periodIndex, + id.periodUid, id.adGroupIndex, id.adIndexInAdGroup, contentPositionUs, id.windowSequenceNumber); } else { - return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, id.windowSequenceNumber); + return getMediaPeriodInfoForContent(id.periodUid, startPositionUs, id.windowSequenceNumber); } } private MediaPeriodInfo getMediaPeriodInfoForAd( - int periodIndex, + Object periodUid, int adGroupIndex, int adIndexInAdGroup, long contentPositionUs, long windowSequenceNumber) { MediaPeriodId id = - new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); + new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = timeline - .getPeriod(id.periodIndex, period) + .getPeriodByUid(id.periodUid, period) .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); long startPositionUs = adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex) @@ -708,14 +697,14 @@ import com.google.android.exoplayer2.util.Assertions; } private MediaPeriodInfo getMediaPeriodInfoForContent( - int periodIndex, long startPositionUs, long windowSequenceNumber) { + Object periodUid, long startPositionUs, long windowSequenceNumber) { int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); long endPositionUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE : period.getAdGroupTimeUs(nextAdGroupIndex); - MediaPeriodId id = new MediaPeriodId(periodIndex, windowSequenceNumber, endPositionUs); - timeline.getPeriod(id.periodIndex, period); + MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); + timeline.getPeriodByUid(id.periodUid, period); boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = @@ -725,7 +714,7 @@ import com.google.android.exoplayer2.util.Assertions; } private boolean isLastInPeriod(MediaPeriodId id) { - int adGroupCount = timeline.getPeriod(id.periodIndex, period).getAdGroupCount(); + int adGroupCount = timeline.getPeriodByUid(id.periodUid, period).getAdGroupCount(); if (adGroupCount == 0) { return true; } @@ -749,9 +738,10 @@ import com.google.android.exoplayer2.util.Assertions; } private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { - int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex; + int periodIndex = timeline.getIndexOfPeriod(id.periodUid); + int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex; return !timeline.getWindow(windowIndex, window).isDynamic - && timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, shuffleModeEnabled) + && timeline.isLastPeriod(periodIndex, period, window, repeatMode, shuffleModeEnabled) && isLastMediaPeriodInPeriod; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index b338de15b4..02058c0484 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -29,7 +29,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * Dummy media period id used while the timeline is empty and no period id is specified. This id * is used when playback infos are created with {@link #createDummy(long, TrackSelectorResult)}. */ - public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = new MediaPeriodId(/* periodIndex= */ 0); + public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = + new MediaPeriodId(/* periodUid= */ new Object()); /** The current {@link Timeline}. */ public final Timeline timeline; @@ -149,23 +150,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } - public PlaybackInfo copyWithPeriodIndex(int periodIndex) { - return new PlaybackInfo( - timeline, - manifest, - periodId.copyWithPeriodIndex(periodIndex), - startPositionUs, - contentPositionUs, - playbackState, - isLoading, - trackGroups, - trackSelectorResult, - loadingMediaPeriodId, - bufferedPositionUs, - totalBufferedDurationUs, - positionUs); - } - public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { return new PlaybackInfo( timeline, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index a1a0e9b152..1639920aaa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -702,13 +702,13 @@ public abstract class Timeline { * Calls {@link #getPeriodPosition(Window, Period, int, long, long)} with a zero default position * projection. */ - public final Pair getPeriodPosition(Window window, Period period, int windowIndex, - long windowPositionUs) { + public final Pair getPeriodPosition( + Window window, Period period, int windowIndex, long windowPositionUs) { return getPeriodPosition(window, period, windowIndex, windowPositionUs, 0); } /** - * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs). + * Converts (windowIndex, windowPositionUs) to the corresponding (periodUid, periodPositionUs). * * @param window A {@link Window} that may be overwritten. * @param period A {@link Period} that may be overwritten. @@ -717,12 +717,16 @@ public abstract class Timeline { * start position. * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the * duration into the future by which the window's position should be projected. - * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs} + * @return The corresponding (periodUid, periodPositionUs), or null if {@code #windowPositionUs} * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's * position could not be projected by {@code defaultPositionProjectionUs}. */ - public final Pair getPeriodPosition(Window window, Period period, int windowIndex, - long windowPositionUs, long defaultPositionProjectionUs) { + public final Pair getPeriodPosition( + Window window, + Period period, + int windowIndex, + long windowPositionUs, + long defaultPositionProjectionUs) { Assertions.checkIndex(windowIndex, 0, getWindowCount()); getWindow(windowIndex, window, false, defaultPositionProjectionUs); if (windowPositionUs == C.TIME_UNSET) { @@ -733,13 +737,13 @@ public abstract class Timeline { } int periodIndex = window.firstPeriodIndex; long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; - long periodDurationUs = getPeriod(periodIndex, period).getDurationUs(); + long periodDurationUs = getPeriod(periodIndex, period, /* setIds= */ true).getDurationUs(); while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs && periodIndex < window.lastPeriodIndex) { periodPositionUs -= periodDurationUs; - periodDurationUs = getPeriod(++periodIndex, period).getDurationUs(); + periodDurationUs = getPeriod(++periodIndex, period, /* setIds= */ true).getDurationUs(); } - return Pair.create(periodIndex, periodPositionUs); + return Pair.create(period.uid, periodPositionUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 262187586b..3a158060cf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -705,11 +705,10 @@ public class AnalyticsCollector public @Nullable MediaPeriodId tryResolveWindowIndex(int windowIndex) { MediaPeriodId match = null; if (timeline != null) { - int timelinePeriodCount = timeline.getPeriodCount(); for (int i = 0; i < activeMediaPeriods.size(); i++) { WindowAndMediaPeriodId mediaPeriod = activeMediaPeriods.get(i); - int periodIndex = mediaPeriod.mediaPeriodId.periodIndex; - if (periodIndex < timelinePeriodCount + int periodIndex = timeline.getIndexOfPeriod(mediaPeriod.mediaPeriodId.periodUid); + if (periodIndex != C.INDEX_UNSET && timeline.getPeriod(periodIndex, period).windowIndex == windowIndex) { if (match != null) { // Ambiguous match. @@ -731,10 +730,10 @@ public class AnalyticsCollector public void onTimelineChanged(Timeline timeline) { for (int i = 0; i < activeMediaPeriods.size(); i++) { activeMediaPeriods.set( - i, updateMediaPeriodToNewTimeline(activeMediaPeriods.get(i), timeline)); + i, updateWindowIndexToNewTimeline(activeMediaPeriods.get(i), timeline)); } if (readingMediaPeriod != null) { - readingMediaPeriod = updateMediaPeriodToNewTimeline(readingMediaPeriod, timeline); + readingMediaPeriod = updateWindowIndexToNewTimeline(readingMediaPeriod, timeline); } this.timeline = timeline; updateLastReportedPlayingMediaPeriod(); @@ -779,19 +778,17 @@ public class AnalyticsCollector } } - private WindowAndMediaPeriodId updateMediaPeriodToNewTimeline( + private WindowAndMediaPeriodId updateWindowIndexToNewTimeline( WindowAndMediaPeriodId mediaPeriod, Timeline newTimeline) { if (newTimeline.isEmpty() || timeline.isEmpty()) { return mediaPeriod; } - Object uid = timeline.getUidOfPeriod(mediaPeriod.mediaPeriodId.periodIndex); - int newPeriodIndex = newTimeline.getIndexOfPeriod(uid); + int newPeriodIndex = newTimeline.getIndexOfPeriod(mediaPeriod.mediaPeriodId.periodUid); if (newPeriodIndex == C.INDEX_UNSET) { return mediaPeriod; } int newWindowIndex = newTimeline.getPeriod(newPeriodIndex, period).windowIndex; - return new WindowAndMediaPeriodId( - newWindowIndex, mediaPeriod.mediaPeriodId.copyWithPeriodIndex(newPeriodIndex)); + return new WindowAndMediaPeriodId(newWindowIndex, mediaPeriod.mediaPeriodId); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java index 305a249e4a..4a3505749a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -29,6 +29,37 @@ import com.google.android.exoplayer2.Timeline; private final ShuffleOrder shuffleOrder; private final boolean isAtomic; + /** + * Returns UID of child timeline from a concatenated period UID. + * + * @param concatenatedUid UID of a period in a concatenated timeline. + * @return UID of the child timeline this period belongs to. + */ + public static Object getChildTimelineUidFromConcatenatedUid(Object concatenatedUid) { + return ((Pair) concatenatedUid).first; + } + + /** + * Returns UID of the period in the child timeline from a concatenated period UID. + * + * @param concatenatedUid UID of a period in a concatenated timeline. + * @return UID of the period in the child timeline. + */ + public static Object getChildPeriodUidFromConcatenatedUid(Object concatenatedUid) { + return ((Pair) concatenatedUid).second; + } + + /** + * Returns concatenated UID for a period in a child timeline. + * + * @param childTimelineUid UID of the child timeline this period belongs to. + * @param childPeriodUid UID of the period in the child timeline. + * @return UID of the period in the concatenated timeline. + */ + public static Object getConcatenatedUid(Object childTimelineUid, Object childPeriodUid) { + return Pair.create(childTimelineUid, childPeriodUid); + } + /** * Sets up a concatenated timeline with a shuffle order of child timelines. * @@ -170,9 +201,8 @@ import com.google.android.exoplayer2.Timeline; @Override public final Period getPeriodByUid(Object uid, Period period) { - Pair childUidAndPeriodUid = (Pair) uid; - Object childUid = childUidAndPeriodUid.first; - Object periodUid = childUidAndPeriodUid.second; + Object childUid = getChildTimelineUidFromConcatenatedUid(uid); + Object periodUid = getChildPeriodUidFromConcatenatedUid(uid); int childIndex = getChildIndexByChildUid(childUid); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); getTimelineByChildIndex(childIndex).getPeriodByUid(periodUid, period); @@ -190,7 +220,7 @@ import com.google.android.exoplayer2.Timeline; setIds); period.windowIndex += firstWindowIndexInChild; if (setIds) { - period.uid = Pair.create(getChildUidByChildIndex(childIndex), period.uid); + period.uid = getConcatenatedUid(getChildUidByChildIndex(childIndex), period.uid); } return period; } @@ -200,9 +230,8 @@ import com.google.android.exoplayer2.Timeline; if (!(uid instanceof Pair)) { return C.INDEX_UNSET; } - Pair childUidAndPeriodUid = (Pair) uid; - Object childUid = childUidAndPeriodUid.first; - Object periodUid = childUidAndPeriodUid.second; + Object childUid = getChildTimelineUidFromConcatenatedUid(uid); + Object periodUid = getChildPeriodUidFromConcatenatedUid(uid); int childIndex = getChildIndexByChildUid(childUid); if (childIndex == C.INDEX_UNSET) { return C.INDEX_UNSET; @@ -218,7 +247,7 @@ import com.google.android.exoplayer2.Timeline; int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); Object periodUidInChild = getTimelineByChildIndex(childIndex).getUidOfPeriod(periodIndex - firstPeriodIndexInChild); - return Pair.create(getChildUidByChildIndex(childIndex), periodUidInChild); + return getConcatenatedUid(getChildUidByChildIndex(childIndex), periodUidInChild); } /** 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 8987e9cb56..9850427063 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 @@ -60,8 +60,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource mediaSourceHolders; - private final MediaSourceHolder query; private final Map mediaSourceByMediaPeriod; + private final Map mediaSourceByUid; private final List pendingOnCompletionActions; private final boolean isAtomic; private final boolean useLazyPreparation; @@ -125,10 +125,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource 0 ? shuffleOrder.cloneAndClear() : shuffleOrder; this.mediaSourceByMediaPeriod = new IdentityHashMap<>(); + this.mediaSourceByUid = new HashMap<>(); this.mediaSourcesPublic = new ArrayList<>(); this.mediaSourceHolders = new ArrayList<>(); this.pendingOnCompletionActions = new ArrayList<>(); - this.query = new MediaSourceHolder(/* mediaSource= */ null); this.isAtomic = isAtomic; this.useLazyPreparation = useLazyPreparation; window = new Timeline.Window(); @@ -451,8 +451,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource { private final Map childMediaPeriodIdToMediaPeriodId; private final Map mediaPeriodToChildMediaPeriodId; - private int childPeriodCount; - /** * Loops the provided source indefinitely. Note that it is usually better to use * {@link ExoPlayer#setRepeatMode(int)}. @@ -80,7 +78,8 @@ public final class LoopingMediaSource extends CompositeMediaSource { if (loopCount == Integer.MAX_VALUE) { return childSource.createPeriod(id, allocator); } - MediaPeriodId childMediaPeriodId = id.copyWithPeriodIndex(id.periodIndex % childPeriodCount); + Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid); + MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid); childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id); MediaPeriod mediaPeriod = childSource.createPeriod(childMediaPeriodId, allocator); mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId); @@ -96,16 +95,9 @@ public final class LoopingMediaSource extends CompositeMediaSource { } } - @Override - public void releaseSourceInternal() { - super.releaseSourceInternal(); - childPeriodCount = 0; - } - @Override protected void onChildSourceInfoRefreshed( Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { - childPeriodCount = timeline.getPeriodCount(); Timeline loopingTimeline = loopCount != Integer.MAX_VALUE ? new LoopingTimeline(timeline, loopCount) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index fb4c64ae6e..6b0f5c8eeb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -66,10 +66,8 @@ public interface MediaSource { */ final class MediaPeriodId { - /** - * The timeline period index. - */ - public final int periodIndex; + /** The unique id of the timeline period. */ + public final Object periodUid; /** * If the media period is in an ad group, the index of the ad group in the period. @@ -103,72 +101,70 @@ public interface MediaSource { * Creates a media period identifier for a dummy period which is not part of a buffered sequence * of windows. * - * @param periodIndex The period index. + * @param periodUid The unique id of the timeline period. */ - public MediaPeriodId(int periodIndex) { - this(periodIndex, C.INDEX_UNSET); + public MediaPeriodId(Object periodUid) { + this(periodUid, C.INDEX_UNSET); } /** * Creates a media period identifier for the specified period in the timeline. * - * @param periodIndex The timeline period index. + * @param periodUid The unique id of the timeline period. * @param windowSequenceNumber The sequence number of the window in the buffered sequence of * windows this media period is part of. */ - public MediaPeriodId(int periodIndex, long windowSequenceNumber) { - this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_END_OF_SOURCE); + public MediaPeriodId(Object periodUid, long windowSequenceNumber) { + this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_END_OF_SOURCE); } /** * Creates a media period identifier for the specified clipped period in the timeline. * - * @param periodIndex The timeline period index. + * @param periodUid The unique id of the timeline period. * @param windowSequenceNumber The sequence number of the window in the buffered sequence of * windows this media period is part of. * @param endPositionUs The end position of the media period within the timeline period, in * microseconds. */ - public MediaPeriodId(int periodIndex, long windowSequenceNumber, long endPositionUs) { - this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, endPositionUs); + public MediaPeriodId(Object periodUid, long windowSequenceNumber, long endPositionUs) { + this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, endPositionUs); } /** * Creates a media period identifier that identifies an ad within an ad group at the specified * timeline period. * - * @param periodIndex The index of the timeline period that contains the ad group. + * @param periodUid The unique id of the timeline period that contains the ad group. * @param adGroupIndex The index of the ad group. * @param adIndexInAdGroup The index of the ad in the ad group. * @param windowSequenceNumber The sequence number of the window in the buffered sequence of * windows this media period is part of. */ public MediaPeriodId( - int periodIndex, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { - this(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_END_OF_SOURCE); + Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { + this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_END_OF_SOURCE); } private MediaPeriodId( - int periodIndex, + Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber, long endPositionUs) { - this.periodIndex = periodIndex; + this.periodUid = periodUid; this.adGroupIndex = adGroupIndex; this.adIndexInAdGroup = adIndexInAdGroup; this.windowSequenceNumber = windowSequenceNumber; this.endPositionUs = endPositionUs; } - /** - * Returns a copy of this period identifier but with {@code newPeriodIndex} as its period index. - */ - public MediaPeriodId copyWithPeriodIndex(int newPeriodIndex) { - return periodIndex == newPeriodIndex + /** Returns a copy of this period identifier but with {@code newPeriodUid} as its period uid. */ + public MediaPeriodId copyWithPeriodUid(Object newPeriodUid) { + return periodUid.equals(newPeriodUid) ? this : new MediaPeriodId( - newPeriodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, endPositionUs); + newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, endPositionUs); } /** @@ -188,7 +184,7 @@ public interface MediaSource { } MediaPeriodId periodId = (MediaPeriodId) obj; - return periodIndex == periodId.periodIndex + return periodUid.equals(periodId.periodUid) && adGroupIndex == periodId.adGroupIndex && adIndexInAdGroup == periodId.adIndexInAdGroup && windowSequenceNumber == periodId.windowSequenceNumber @@ -198,14 +194,13 @@ public interface MediaSource { @Override public int hashCode() { int result = 17; - result = 31 * result + periodIndex; + result = 31 * result + periodUid.hashCode(); result = 31 * result + adGroupIndex; result = 31 * result + adIndexInAdGroup; result = 31 * result + (int) windowSequenceNumber; result = 31 * result + (int) endPositionUs; return result; } - } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index d33cfb8abd..b1367cb19f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -68,10 +68,10 @@ public final class MergingMediaSource extends CompositeMediaSource { private static final int PERIOD_COUNT_UNSET = -1; private final MediaSource[] mediaSources; + private final Timeline[] timelines; private final ArrayList pendingTimelineSources; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; - private Timeline primaryTimeline; private Object primaryManifest; private int periodCount; private IllegalMergeException mergeError; @@ -95,6 +95,7 @@ public final class MergingMediaSource extends CompositeMediaSource { this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; pendingTimelineSources = new ArrayList<>(Arrays.asList(mediaSources)); periodCount = PERIOD_COUNT_UNSET; + timelines = new Timeline[mediaSources.length]; } @Override @@ -119,8 +120,11 @@ public final class MergingMediaSource extends CompositeMediaSource { @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; + int periodIndex = timelines[0].getIndexOfPeriod(id.periodUid); for (int i = 0; i < periods.length; i++) { - periods[i] = mediaSources[i].createPeriod(id, allocator); + MediaPeriodId childMediaPeriodId = + id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex)); + periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator); } return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods); } @@ -136,7 +140,7 @@ public final class MergingMediaSource extends CompositeMediaSource { @Override public void releaseSourceInternal() { super.releaseSourceInternal(); - primaryTimeline = null; + Arrays.fill(timelines, null); primaryManifest = null; periodCount = PERIOD_COUNT_UNSET; mergeError = null; @@ -154,12 +158,12 @@ public final class MergingMediaSource extends CompositeMediaSource { return; } pendingTimelineSources.remove(mediaSource); + timelines[id] = timeline; if (mediaSource == mediaSources[0]) { - primaryTimeline = timeline; primaryManifest = manifest; } if (pendingTimelineSources.isEmpty()) { - refreshSourceInfo(primaryTimeline, primaryManifest); + refreshSourceInfo(timelines[0], primaryManifest); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 24f49cb086..c75c9541a4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -308,7 +308,6 @@ public final class SingleSampleMediaSource extends BaseMediaSource { @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { - Assertions.checkArgument(id.periodIndex == 0); return new SingleSampleMediaPeriod( dataSpec, dataSourceFactory, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index e3df34bed8..0e218d5efe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -176,7 +176,7 @@ public final class AdsMediaSource extends CompositeMediaSource { // Used to identify the content "child" source for CompositeMediaSource. private static final MediaPeriodId DUMMY_CONTENT_MEDIA_PERIOD_ID = - new MediaPeriodId(/* periodIndex= */ 0); + new MediaPeriodId(/* periodUid= */ new Object()); private final MediaSource contentMediaSource; private final MediaSourceFactory adMediaSourceFactory; @@ -194,7 +194,7 @@ public final class AdsMediaSource extends CompositeMediaSource { private Object contentManifest; private AdPlaybackState adPlaybackState; private MediaSource[][] adGroupMediaSources; - private long[][] adDurationsUs; + private Timeline[][] adGroupTimelines; /** * Constructs a new source that inserts ads linearly with the content specified by {@code @@ -309,7 +309,7 @@ public final class AdsMediaSource extends CompositeMediaSource { deferredMediaPeriodByAdMediaSource = new HashMap<>(); period = new Timeline.Period(); adGroupMediaSources = new MediaSource[0][]; - adDurationsUs = new long[0][]; + adGroupTimelines = new Timeline[0][]; adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes()); } @@ -341,8 +341,7 @@ public final class AdsMediaSource extends CompositeMediaSource { int adCount = adIndexInAdGroup + 1; adGroupMediaSources[adGroupIndex] = Arrays.copyOf(adGroupMediaSources[adGroupIndex], adCount); - adDurationsUs[adGroupIndex] = Arrays.copyOf(adDurationsUs[adGroupIndex], adCount); - Arrays.fill(adDurationsUs[adGroupIndex], oldAdCount, adCount, C.TIME_UNSET); + adGroupTimelines[adGroupIndex] = Arrays.copyOf(adGroupTimelines[adGroupIndex], adCount); } adGroupMediaSources[adGroupIndex][adIndexInAdGroup] = adMediaSource; deferredMediaPeriodByAdMediaSource.put(adMediaSource, new ArrayList<>()); @@ -354,8 +353,9 @@ public final class AdsMediaSource extends CompositeMediaSource { new AdPrepareErrorListener(adUri, adGroupIndex, adIndexInAdGroup)); List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); if (mediaPeriods == null) { - MediaPeriodId adSourceMediaPeriodId = - new MediaPeriodId(/* periodIndex= */ 0, id.windowSequenceNumber); + Object periodUid = + adGroupTimelines[adGroupIndex][adIndexInAdGroup].getUidOfPeriod(/* periodIndex= */ 0); + MediaPeriodId adSourceMediaPeriodId = new MediaPeriodId(periodUid, id.windowSequenceNumber); deferredMediaPeriod.createPeriod(adSourceMediaPeriodId); } else { // Keep track of the deferred media period so it can be populated with the real media period @@ -391,7 +391,7 @@ public final class AdsMediaSource extends CompositeMediaSource { contentManifest = null; adPlaybackState = null; adGroupMediaSources = new MediaSource[0][]; - adDurationsUs = new long[0][]; + adGroupTimelines = new Timeline[0][]; mainHandler.post(adsLoader::detachPlayer); } @@ -424,8 +424,8 @@ public final class AdsMediaSource extends CompositeMediaSource { if (this.adPlaybackState == null) { adGroupMediaSources = new MediaSource[adPlaybackState.adGroupCount][]; Arrays.fill(adGroupMediaSources, new MediaSource[0]); - adDurationsUs = new long[adPlaybackState.adGroupCount][]; - Arrays.fill(adDurationsUs, new long[0]); + adGroupTimelines = new Timeline[adPlaybackState.adGroupCount][]; + Arrays.fill(adGroupTimelines, new Timeline[0]); } this.adPlaybackState = adPlaybackState; maybeUpdateSourceInfo(); @@ -440,13 +440,14 @@ public final class AdsMediaSource extends CompositeMediaSource { private void onAdSourceInfoRefreshed(MediaSource mediaSource, int adGroupIndex, int adIndexInAdGroup, Timeline timeline) { Assertions.checkArgument(timeline.getPeriodCount() == 1); - adDurationsUs[adGroupIndex][adIndexInAdGroup] = timeline.getPeriod(0, period).getDurationUs(); + adGroupTimelines[adGroupIndex][adIndexInAdGroup] = timeline; List mediaPeriods = deferredMediaPeriodByAdMediaSource.remove(mediaSource); if (mediaPeriods != null) { + Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); for (int i = 0; i < mediaPeriods.size(); i++) { DeferredMediaPeriod mediaPeriod = mediaPeriods.get(i); MediaPeriodId adSourceMediaPeriodId = - new MediaPeriodId(/* periodIndex= */ 0, mediaPeriod.id.windowSequenceNumber); + new MediaPeriodId(periodUid, mediaPeriod.id.windowSequenceNumber); mediaPeriod.createPeriod(adSourceMediaPeriodId); } } @@ -455,7 +456,7 @@ public final class AdsMediaSource extends CompositeMediaSource { private void maybeUpdateSourceInfo() { if (adPlaybackState != null && contentTimeline != null) { - adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs); + adPlaybackState = adPlaybackState.withAdDurationsUs(getAdDurations(adGroupTimelines, period)); Timeline timeline = adPlaybackState.adGroupCount == 0 ? contentTimeline @@ -464,6 +465,20 @@ public final class AdsMediaSource extends CompositeMediaSource { } } + private static long[][] getAdDurations(Timeline[][] adTimelines, Timeline.Period period) { + long[][] adDurations = new long[adTimelines.length][]; + for (int i = 0; i < adTimelines.length; i++) { + adDurations[i] = new long[adTimelines[i].length]; + for (int j = 0; j < adTimelines[i].length; j++) { + adDurations[i][j] = + adTimelines[i][j] == null + ? C.TIME_UNSET + : adTimelines[i][j].getPeriod(/* periodIndex= */ 0, period).getDurationUs(); + } + } + return adDurations; + } + /** Listener for component events. All methods are called on the main thread. */ private final class ComponentListener implements AdsLoader.EventListener { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 3ca463e5e4..0324630f1f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -456,7 +456,8 @@ public class EventLogger implements AnalyticsListener { private String getEventTimeString(EventTime eventTime) { String windowPeriodString = "window=" + eventTime.windowIndex; if (eventTime.mediaPeriodId != null) { - windowPeriodString += ", period=" + eventTime.mediaPeriodId.periodIndex; + windowPeriodString += + ", period=" + eventTime.timeline.getIndexOfPeriod(eventTime.mediaPeriodId.periodUid); if (eventTime.mediaPeriodId.isAd()) { windowPeriodString += ", adGroup=" + eventTime.mediaPeriodId.adGroupIndex; windowPeriodString += ", ad=" + eventTime.mediaPeriodId.adIndexInAdGroup; 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 5cba9267b5..6409c1b604 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 @@ -187,10 +187,28 @@ public final class ExoPlayerTest { @Test public void testReadAheadToEndDoesNotResetRenderer() throws Exception { // Use sufficiently short periods to ensure the player attempts to read all at once. - TimelineWindowDefinition windowDefinition = + TimelineWindowDefinition windowDefinition0 = new TimelineWindowDefinition( - /* isSeekable= */ false, /* isDynamic= */ false, /* durationUs= */ 100_000); - Timeline timeline = new FakeTimeline(windowDefinition, windowDefinition, windowDefinition); + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationUs= */ 100_000); + TimelineWindowDefinition windowDefinition1 = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationUs= */ 100_000); + TimelineWindowDefinition windowDefinition2 = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 2, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationUs= */ 100_000); + Timeline timeline = new FakeTimeline(windowDefinition0, windowDefinition1, windowDefinition2); final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) { @@ -2019,9 +2037,12 @@ public final class ExoPlayerTest { // Assert that the second period was re-created from the new timeline. assertThat(mediaSource.getCreatedMediaPeriods()) .containsExactly( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 2)) + new MediaPeriodId( + timeline1.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0), + new MediaPeriodId( + timeline1.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1), + new MediaPeriodId( + timeline2.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 2)) .inOrder(); } @@ -2057,10 +2078,14 @@ public final class ExoPlayerTest { testRunner.assertPlayedPeriodIndices(0, 1, 0, 1); assertThat(mediaSource.getCreatedMediaPeriods()) .containsAllOf( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0), + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); assertThat(mediaSource.getCreatedMediaPeriods()) - .doesNotContain(new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1)); + .doesNotContain( + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index 8a2612cacc..9ec22c0d51 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -108,28 +108,16 @@ public final class AnalyticsCollectorTest { new EventWindowAndPeriodId(/* windowIndex= */ 0, /* mediaPeriodId= */ null); private static final EventWindowAndPeriodId WINDOW_1 = new EventWindowAndPeriodId(/* windowIndex= */ 1, /* mediaPeriodId= */ null); - private static final EventWindowAndPeriodId PERIOD_0 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); - private static final EventWindowAndPeriodId PERIOD_1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1)); - private static final EventWindowAndPeriodId PERIOD_0_SEQ_0 = PERIOD_0; - private static final EventWindowAndPeriodId PERIOD_1_SEQ_1 = PERIOD_1; - private static final EventWindowAndPeriodId PERIOD_0_SEQ_1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 1)); - private static final EventWindowAndPeriodId PERIOD_1_SEQ_0 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); - private static final EventWindowAndPeriodId PERIOD_1_SEQ_2 = - new EventWindowAndPeriodId( - /* windowIndex= */ 1, - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 2)); + + private EventWindowAndPeriodId period0; + private EventWindowAndPeriodId period1; + private EventWindowAndPeriodId period0Seq0; + private EventWindowAndPeriodId period1Seq1; + private EventWindowAndPeriodId period0Seq1; + private EventWindowAndPeriodId period1Seq0; + private EventWindowAndPeriodId period1Seq2; + private EventWindowAndPeriodId window0Period1Seq0; + private EventWindowAndPeriodId window1Period0Seq1; @Test public void testEmptyTimeline() throws Exception { @@ -155,34 +143,35 @@ public final class AnalyticsCollectorTest { Builder.AUDIO_FORMAT); TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + populateEventIds(SINGLE_PERIOD_TIMELINE); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady */, WINDOW_0 /* BUFFERING */, - PERIOD_0 /* READY */, - PERIOD_0 /* ENDED */); + period0 /* READY */, + period0 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0 /* started */, PERIOD_0 /* stopped */); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0); + .containsExactly(period0 /* started */, period0 /* stopped */); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) - .containsExactly(WINDOW_0 /* manifest */, PERIOD_0 /* media */); + .containsExactly(WINDOW_0 /* manifest */, period0 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) - .containsExactly(WINDOW_0 /* manifest */, PERIOD_0 /* media */); + .containsExactly(WINDOW_0 /* manifest */, period0 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* audio */, PERIOD_0 /* video */); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(PERIOD_0); + .containsExactly(period0 /* audio */, period0 /* video */); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0 /* audio */, PERIOD_0 /* video */); + .containsExactly(period0 /* audio */, period0 /* video */); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0 /* audio */, PERIOD_0 /* video */); + .containsExactly(period0 /* audio */, period0 /* video */); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* audio */, PERIOD_0 /* video */); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(PERIOD_0); + .containsExactly(period0 /* audio */, period0 /* video */); + assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); listener.assertNoMoreEvents(); } @@ -202,47 +191,48 @@ public final class AnalyticsCollectorTest { Builder.AUDIO_FORMAT)); TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady */, WINDOW_0 /* BUFFERING */, - PERIOD_0 /* READY */, - PERIOD_1 /* ENDED */); + period0 /* READY */, + period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(PERIOD_1); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_0, PERIOD_0, PERIOD_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0, PERIOD_1); + .containsExactly(period0, period0, period0, period0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) .containsExactly( - PERIOD_0 /* audio */, PERIOD_0 /* video */, PERIOD_1 /* audio */, PERIOD_1 /* video */); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(PERIOD_0, PERIOD_1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(PERIOD_0, PERIOD_1); + period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(period0, period1); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0 /* audio */, PERIOD_0 /* video */); + .containsExactly(period0 /* audio */, period0 /* video */); assertThat(listener.getEvents(EVENT_DECODER_INIT)) .containsExactly( - PERIOD_0 /* audio */, PERIOD_0 /* video */, PERIOD_1 /* audio */, PERIOD_1 /* video */); + period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) .containsExactly( - PERIOD_0 /* audio */, PERIOD_0 /* video */, PERIOD_1 /* audio */, PERIOD_1 /* video */); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(PERIOD_1); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(PERIOD_0); + period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */); + assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); listener.assertNoMoreEvents(); } @@ -255,47 +245,48 @@ public final class AnalyticsCollectorTest { SINGLE_PERIOD_TIMELINE, /* manifest= */ null, Builder.AUDIO_FORMAT)); TestAnalyticsListener listener = runAnalyticsTest(mediaSource); + populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady */, WINDOW_0 /* BUFFERING */, - PERIOD_0 /* READY */, - PERIOD_1 /* BUFFERING */, - PERIOD_1 /* READY */, - PERIOD_1 /* ENDED */); + period0 /* READY */, + period1 /* BUFFERING */, + period1 /* READY */, + period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(PERIOD_1); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_0, PERIOD_0, PERIOD_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0, PERIOD_1); + .containsExactly(period0, period0, period0, period0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(PERIOD_0, PERIOD_1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(PERIOD_0, PERIOD_1); + .containsExactly(period0 /* video */, period1 /* audio */); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(period0, period1); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); + .containsExactly(period0 /* video */, period1 /* audio */); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); + .containsExactly(period0 /* video */, period1 /* audio */); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(PERIOD_1); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(PERIOD_0); + .containsExactly(period0 /* video */, period1 /* audio */); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); listener.assertNoMoreEvents(); } @@ -315,51 +306,52 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* setPlayWhenReady=false */, - PERIOD_0 /* READY */, - PERIOD_1 /* BUFFERING */, - PERIOD_1 /* READY */, - PERIOD_1 /* setPlayWhenReady=true */, - PERIOD_1 /* ENDED */); + period0 /* READY */, + period1 /* BUFFERING */, + period1 /* READY */, + period1 /* setPlayWhenReady=true */, + period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(PERIOD_1); - assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(PERIOD_1); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); + assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period1); List loadingEvents = listener.getEvents(EVENT_LOADING_CHANGED); assertThat(loadingEvents).hasSize(4); - assertThat(loadingEvents).containsAllOf(PERIOD_0, PERIOD_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0, PERIOD_1); + assertThat(loadingEvents).containsAllOf(period0, period0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1 /* media */); + period1 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(PERIOD_0, PERIOD_1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(PERIOD_0, PERIOD_1); + .containsExactly(period0 /* video */, period1 /* audio */); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(period0, period1); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0, period1); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); + .containsExactly(period0 /* video */, period1 /* audio */); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); + .containsExactly(period0 /* video */, period1 /* audio */); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0 /* video */, PERIOD_1 /* audio */); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(PERIOD_1); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(PERIOD_0); + .containsExactly(period0 /* video */, period1 /* audio */); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); listener.assertNoMoreEvents(); } @@ -386,64 +378,64 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* setPlayWhenReady=false */, - PERIOD_0 /* READY */, - PERIOD_0 /* setPlayWhenReady=true */, - PERIOD_0 /* setPlayWhenReady=false */, - PERIOD_0 /* BUFFERING */, - PERIOD_0 /* READY */, - PERIOD_0 /* setPlayWhenReady=true */, - PERIOD_1_SEQ_2 /* BUFFERING */, - PERIOD_1_SEQ_2 /* READY */, - PERIOD_1_SEQ_2 /* ENDED */); + period0 /* READY */, + period0 /* setPlayWhenReady=true */, + period0 /* setPlayWhenReady=false */, + period0 /* BUFFERING */, + period0 /* READY */, + period0 /* setPlayWhenReady=true */, + period1Seq2 /* BUFFERING */, + period1Seq2 /* READY */, + period1Seq2 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_2); - assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(PERIOD_0); + .containsExactly(period0, period1Seq2); + assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_0, PERIOD_0, PERIOD_0, PERIOD_0, PERIOD_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0, PERIOD_1_SEQ_2); + .containsExactly(period0, period0, period0, period0, period0, period0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0, period1Seq2); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1_SEQ_1 /* media */, - PERIOD_1_SEQ_2 /* media */); + period1Seq1 /* media */, + period1Seq2 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0 /* media */, + period0 /* media */, WINDOW_1 /* manifest */, - PERIOD_1_SEQ_1 /* media */, - PERIOD_1_SEQ_2 /* media */); + period1Seq1 /* media */, + period1Seq2 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1, PERIOD_1_SEQ_2, PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2); assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1, PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq1, period1Seq2); assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1); + .containsExactly(period0, period1Seq1); assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1, PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq1, period1Seq2); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0, PERIOD_0, PERIOD_1_SEQ_2); + .containsExactly(period0, period0, period1Seq2); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1, PERIOD_1_SEQ_2, PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_1, PERIOD_1_SEQ_2, PERIOD_1_SEQ_2); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) - .containsExactly(PERIOD_0, PERIOD_0, PERIOD_1_SEQ_2); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_2); + .containsExactly(period0, period0, period1Seq2); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period1Seq2); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(PERIOD_0, PERIOD_1_SEQ_2); + .containsExactly(period0, period1Seq2); listener.assertNoMoreEvents(); } @@ -462,54 +454,52 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource1, actionSchedule); + populateEventIds(SINGLE_PERIOD_TIMELINE); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* setPlayWhenReady=false */, - PERIOD_0_SEQ_0 /* READY */, + period0Seq0 /* READY */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* setPlayWhenReady=true */, - PERIOD_0_SEQ_1 /* READY */, - PERIOD_0_SEQ_1 /* ENDED */); + period0Seq1 /* READY */, + period0Seq1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* reset */, WINDOW_0 /* prepared */); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0, PERIOD_0_SEQ_1, PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq0, period0Seq1, period0Seq1); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly( - PERIOD_0_SEQ_0 /* prepared */, WINDOW_0 /* reset */, PERIOD_0_SEQ_1 /* prepared */); + period0Seq0 /* prepared */, WINDOW_0 /* reset */, period0Seq1 /* prepared */); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */, + period0Seq0 /* media */, WINDOW_0 /* manifest */, - PERIOD_0_SEQ_1 /* media */); + period0Seq1 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */, + period0Seq0 /* media */, WINDOW_0 /* manifest */, - PERIOD_0_SEQ_1 /* media */); + period0Seq1 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq1); assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); - assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); - assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq1); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0Seq0, period0Seq1); + assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(period0Seq0, period0Seq1); + assertThat(listener.getEvents(EVENT_DECODER_INIT)).containsExactly(period0Seq0, period0Seq1); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq1); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq1); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_1); + .containsExactly(period0Seq0, period0Seq1); listener.assertNoMoreEvents(); } @@ -527,55 +517,51 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); + populateEventIds(SINGLE_PERIOD_TIMELINE); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, - PERIOD_0_SEQ_0 /* READY */, + period0Seq0 /* READY */, WINDOW_0 /* IDLE */, WINDOW_0 /* BUFFERING */, - PERIOD_0_SEQ_0 /* READY */, - PERIOD_0_SEQ_0 /* ENDED */); - // assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)).doesNotContain(PERIOD_0_SEQ_1); + period0Seq0 /* READY */, + period0Seq0 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* prepared */); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0, PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(WINDOW_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */, + period0Seq0 /* media */, WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */); + period0Seq0 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */, + period0Seq0 /* media */, WINDOW_0 /* manifest */, - PERIOD_0_SEQ_0 /* media */); + period0Seq0 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq0); + assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_DECODER_INIT)).containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(period0Seq0, period0Seq0); listener.assertNoMoreEvents(); } @@ -602,45 +588,50 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(concatenatedMediaSource, actionSchedule); + populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* setPlayWhenReady=false */, - PERIOD_0_SEQ_0 /* READY */, - PERIOD_0_SEQ_0 /* setPlayWhenReady=true */, - PERIOD_0_SEQ_0 /* setPlayWhenReady=false */, - PERIOD_1_SEQ_0 /* setPlayWhenReady=true */, - PERIOD_1_SEQ_0 /* BUFFERING */, - PERIOD_1_SEQ_0 /* ENDED */); - assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0, PERIOD_1_SEQ_0); + window0Period1Seq0 /* READY */, + window0Period1Seq0 /* setPlayWhenReady=true */, + window0Period1Seq0 /* setPlayWhenReady=false */, + period1Seq0 /* setPlayWhenReady=true */, + period1Seq0 /* BUFFERING */, + period1Seq0 /* ENDED */); + assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)).containsExactly(WINDOW_0, period1Seq0); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0, PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(PERIOD_0_SEQ_0); + .containsExactly( + window0Period1Seq0, window0Period1Seq0, window0Period1Seq0, window0Period1Seq0); + assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(window0Period1Seq0); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( - WINDOW_0 /* manifest */, PERIOD_0_SEQ_0 /* media */, PERIOD_1_SEQ_1 /* media */); + WINDOW_0 /* manifest */, + window0Period1Seq0 /* media */, + window1Period0Seq1 /* media */); assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) .containsExactly( - WINDOW_0 /* manifest */, PERIOD_0_SEQ_0 /* media */, PERIOD_1_SEQ_1 /* media */); + WINDOW_0 /* manifest */, + window0Period1Seq0 /* media */, + window1Period0Seq1 /* media */); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_1_SEQ_1); + .containsExactly(window0Period1Seq0, window1Period0Seq1); assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_1_SEQ_1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(PERIOD_0_SEQ_1); + .containsExactly(window0Period1Seq0, window1Period0Seq1); + assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq1); assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_1_SEQ_1); + .containsExactly(window0Period1Seq0, window1Period0Seq1); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_0_SEQ_0); + .containsExactly(window0Period1Seq0, window0Period1Seq0); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_1_SEQ_1); + .containsExactly(window0Period1Seq0, window1Period0Seq1); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(PERIOD_0_SEQ_0, PERIOD_1_SEQ_1); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(PERIOD_0_SEQ_0); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(PERIOD_0_SEQ_0); + .containsExactly(window0Period1Seq0, window1Period0Seq1); + assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(window0Period1Seq0); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(window0Period1Seq0); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(window0Period1Seq0); + assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(window0Period1Seq0); listener.assertNoMoreEvents(); } @@ -663,8 +654,51 @@ public final class AnalyticsCollectorTest { .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); - assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(PERIOD_0); - assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(PERIOD_0); + populateEventIds(SINGLE_PERIOD_TIMELINE); + assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0); + } + + 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) throws Exception { @@ -834,7 +868,7 @@ public final class AnalyticsCollectorTest { + "window=" + windowIndex + ", period=" - + mediaPeriodId.periodIndex + + mediaPeriodId.periodUid + ", sequence=" + mediaPeriodId.windowSequenceNumber + '}' @@ -849,10 +883,13 @@ public final class AnalyticsCollectorTest { private static final class TestAnalyticsListener implements AnalyticsListener { + public Timeline lastReportedTimeline; + private final ArrayList reportedEvents; public TestAnalyticsListener() { reportedEvents = new ArrayList<>(); + lastReportedTimeline = Timeline.EMPTY; } public List getEvents(int eventType) { @@ -880,6 +917,7 @@ public final class AnalyticsCollectorTest { @Override public void onTimelineChanged(EventTime eventTime, int reason) { + lastReportedTimeline = eventTime.timeline; reportedEvents.add(new ReportedEvent(EVENT_TIMELINE_CHANGED, eventTime)); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 1008ac26bf..ee8cdf4887 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -469,11 +469,11 @@ public final class ClippingMediaSourceTest { private static MediaLoadData getClippingMediaSourceMediaLoadData( long clippingStartUs, long clippingEndUs, final long eventStartUs, final long eventEndUs) throws IOException { + Timeline timeline = + new SinglePeriodTimeline( + TEST_PERIOD_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); FakeMediaSource fakeMediaSource = - new FakeMediaSource( - new SinglePeriodTimeline( - TEST_PERIOD_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false), - /* manifest= */ null) { + new FakeMediaSource(timeline, /* manifest= */ null) { @Override protected FakeMediaPeriod createFakeMediaPeriod( MediaPeriodId id, @@ -516,7 +516,8 @@ public final class ClippingMediaSourceTest { testRunner.prepareSource(); // Create period to send the test event configured above. testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); assertThat(reportedMediaLoadData[0]).isNotNull(); } finally { testRunner.release(); @@ -580,7 +581,9 @@ public final class ClippingMediaSourceTest { clippedTimelines[0] = testRunner.prepareSource(); MediaPeriod mediaPeriod = testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + clippedTimelines[0].getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 0)); for (int i = 0; i < additionalTimelines.length; i++) { fakeMediaSource.setNewSourceInfo(additionalTimelines[i], /* newManifest= */ null); clippedTimelines[i + 1] = testRunner.assertTimelineChangeBlocking(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 49b4a0d24f..3ac962f6c3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -274,14 +274,16 @@ public final class ConcatenatingMediaSourceTest { // called yet. MediaPeriod lazyPeriod = testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); CountDownLatch preparedCondition = testRunner.preparePeriod(lazyPeriod, 0); assertThat(preparedCondition.getCount()).isEqualTo(1); // Assert that a second period can also be created and released without problems. MediaPeriod secondLazyPeriod = testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); testRunner.releasePeriod(secondLazyPeriod); // Trigger source info refresh for lazy media source. Assert that now all information is @@ -605,23 +607,27 @@ public final class ConcatenatingMediaSourceTest { // Create all periods and assert period creation of child media sources has been called. testRunner.assertPrepareAndReleaseAllPeriods(); + Object timelineContentOnlyPeriodUid0 = timelineContentOnly.getUidOfPeriod(/* periodIndex= */ 0); + Object timelineContentOnlyPeriodUid1 = timelineContentOnly.getUidOfPeriod(/* periodIndex= */ 1); + Object timelineWithAdsPeriodUid0 = timelineWithAds.getUidOfPeriod(/* periodIndex= */ 0); + Object timelineWithAdsPeriodUid1 = timelineWithAds.getUidOfPeriod(/* periodIndex= */ 1); mediaSourceContentOnly.assertMediaPeriodCreated( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId(timelineContentOnlyPeriodUid0, /* windowSequenceNumber= */ 0)); mediaSourceContentOnly.assertMediaPeriodCreated( - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); + new MediaPeriodId(timelineContentOnlyPeriodUid1, /* windowSequenceNumber= */ 0)); mediaSourceWithAds.assertMediaPeriodCreated( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 1)); + new MediaPeriodId(timelineWithAdsPeriodUid0, /* windowSequenceNumber= */ 1)); mediaSourceWithAds.assertMediaPeriodCreated( - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1)); + new MediaPeriodId(timelineWithAdsPeriodUid1, /* windowSequenceNumber= */ 1)); mediaSourceWithAds.assertMediaPeriodCreated( new MediaPeriodId( - /* periodIndex= */ 0, + timelineWithAdsPeriodUid0, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 1)); mediaSourceWithAds.assertMediaPeriodCreated( new MediaPeriodId( - /* periodIndex= */ 1, + timelineWithAdsPeriodUid1, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 1)); @@ -721,10 +727,11 @@ public final class ConcatenatingMediaSourceTest { public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException { FakeMediaSource childSource = createFakeMediaSource(); mediaSource.addMediaSource(childSource); - testRunner.prepareSource(); + Timeline timeline = testRunner.prepareSource(); MediaPeriod mediaPeriod = testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); mediaSource.removeMediaSource(/* index= */ 0); testRunner.assertTimelineChangeBlocking(); testRunner.releasePeriod(mediaPeriod); @@ -734,8 +741,8 @@ public final class ConcatenatingMediaSourceTest { @Test public void testDuplicateMediaSources() throws IOException, InterruptedException { - FakeMediaSource childSource = - new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2), /* manifest= */ null); + Timeline childTimeline = new FakeTimeline(/* windowCount= */ 2); + FakeMediaSource childSource = new FakeMediaSource(childTimeline, /* manifest= */ null); mediaSource.addMediaSource(childSource); mediaSource.addMediaSource(childSource); @@ -745,16 +752,18 @@ public final class ConcatenatingMediaSourceTest { TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1); testRunner.assertPrepareAndReleaseAllPeriods(); + Object childPeriodUid0 = childTimeline.getUidOfPeriod(/* periodIndex= */ 0); + Object childPeriodUid1 = childTimeline.getUidOfPeriod(/* periodIndex= */ 1); assertThat(childSource.getCreatedMediaPeriods()) .containsAllOf( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 2), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 4), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 6), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 3), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 5), - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 7)); + new MediaPeriodId(childPeriodUid0, /* windowSequenceNumber= */ 0), + new MediaPeriodId(childPeriodUid0, /* windowSequenceNumber= */ 2), + new MediaPeriodId(childPeriodUid0, /* windowSequenceNumber= */ 4), + new MediaPeriodId(childPeriodUid0, /* windowSequenceNumber= */ 6), + new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 1), + new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 3), + new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 5), + new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 7)); // Assert that only one manifest load is reported because the source is reused. testRunner.assertCompletedManifestLoads(/* windowIndices= */ 0); assertCompletedAllMediaPeriodLoads(timeline); @@ -765,8 +774,8 @@ public final class ConcatenatingMediaSourceTest { @Test public void testDuplicateNestedMediaSources() throws IOException, InterruptedException { - FakeMediaSource childSource = - new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), /* manifest= */ null); + Timeline childTimeline = new FakeTimeline(/* windowCount= */ 1); + FakeMediaSource childSource = new FakeMediaSource(childTimeline, /* manifest= */ null); ConcatenatingMediaSource nestedConcatenation = new ConcatenatingMediaSource(); testRunner.prepareSource(); @@ -780,13 +789,14 @@ public final class ConcatenatingMediaSourceTest { TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1); testRunner.assertPrepareAndReleaseAllPeriods(); + Object childPeriodUid = childTimeline.getUidOfPeriod(/* periodIndex= */ 0); assertThat(childSource.getCreatedMediaPeriods()) .containsAllOf( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 1), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 2), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 3), - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 4)); + new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 0), + new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 1), + new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 2), + new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 3), + new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 4)); // Assert that only one manifest load is needed because the source is reused. testRunner.assertCompletedManifestLoads(/* windowIndices= */ 0); assertCompletedAllMediaPeriodLoads(timeline); @@ -844,16 +854,18 @@ public final class ConcatenatingMediaSourceTest { ConcatenatingMediaSource childSource = new ConcatenatingMediaSource(nestedChildSources); mediaSource.addMediaSource(childSource); - testRunner.prepareSource(); + Timeline timeline = testRunner.prepareSource(); MediaPeriod mediaPeriod = testRunner.createPeriod( - new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); childSource.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 1); - testRunner.assertTimelineChangeBlocking(); + timeline = testRunner.assertTimelineChangeBlocking(); testRunner.preparePeriod(mediaPeriod, /* positionUs= */ 0); testRunner.assertCompletedMediaPeriodLoads( - new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); } @Test @@ -882,8 +894,10 @@ public final class ConcatenatingMediaSourceTest { new DefaultShuffleOrder(0), childSources); testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null); - testRunner.prepareSource(); - testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + Timeline timeline = testRunner.prepareSource(); + testRunner.createPeriod( + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); assertThat(childSources[0].isPrepared()).isTrue(); assertThat(childSources[1].isPrepared()).isFalse(); @@ -899,13 +913,16 @@ public final class ConcatenatingMediaSourceTest { new DefaultShuffleOrder(0), childSources); testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null); - testRunner.prepareSource(); + Timeline timeline = testRunner.prepareSource(); // The lazy preparation must only be triggered once, even if we create multiple periods from // the media source. FakeMediaSource.prepareSource asserts that it's not called twice, so // creating two periods shouldn't throw. - testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); - testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + MediaPeriodId mediaPeriodId = + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0); + testRunner.createPeriod(mediaPeriodId); + testRunner.createPeriod(mediaPeriodId); } private void assertCompletedAllMediaPeriodLoads(Timeline timeline) { @@ -918,11 +935,12 @@ public final class ConcatenatingMediaSourceTest { periodIndex <= window.lastPeriodIndex; periodIndex++) { timeline.getPeriod(periodIndex, period); - expectedMediaPeriodIds.add(new MediaPeriodId(periodIndex, windowIndex)); + Object periodUid = timeline.getUidOfPeriod(periodIndex); + expectedMediaPeriodIds.add(new MediaPeriodId(periodUid, windowIndex)); for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) { for (int adIndex = 0; adIndex < period.getAdCountInAdGroup(adGroupIndex); adIndex++) { expectedMediaPeriodIds.add( - new MediaPeriodId(periodIndex, adGroupIndex, adIndex, windowIndex)); + new MediaPeriodId(periodUid, adGroupIndex, adIndex, windowIndex)); } } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java index 2587b78d99..1b57341d33 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java @@ -45,11 +45,11 @@ public final class SinglePeriodTimelineTest { public void testGetPeriodPositionDynamicWindowUnknownDuration() { SinglePeriodTimeline timeline = new SinglePeriodTimeline(C.TIME_UNSET, false, true); // Should return null with any positive position projection. - Pair position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1); + Pair position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1); assertThat(position).isNull(); // Should return (0, 0) without a position projection. position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 0); - assertThat(position.first).isEqualTo(0); + assertThat(position.first).isEqualTo(timeline.getUidOfPeriod(0)); assertThat(position.second).isEqualTo(0); } @@ -66,16 +66,16 @@ public final class SinglePeriodTimelineTest { /* isDynamic= */ true, /* tag= */ null); // Should return null with a positive position projection beyond window duration. - Pair position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, - windowDurationUs + 1); + Pair position = + timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, windowDurationUs + 1); assertThat(position).isNull(); // Should return (0, duration) with a projection equal to window duration. position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, windowDurationUs); - assertThat(position.first).isEqualTo(0); + assertThat(position.first).isEqualTo(timeline.getUidOfPeriod(0)); assertThat(position.second).isEqualTo(windowDurationUs); // Should return (0, 0) without a position projection. position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 0); - assertThat(position.first).isEqualTo(0); + assertThat(position.first).isEqualTo(timeline.getUidOfPeriod(0)); assertThat(position.second).isEqualTo(0); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index d52049931f..a501435262 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -73,8 +73,8 @@ import java.util.List; private final PlayerEmsgHandler playerEmsgHandler; private final IdentityHashMap, PlayerTrackEmsgHandler> trackEmsgHandlerBySampleStream; + private final EventDispatcher eventDispatcher; - private EventDispatcher eventDispatcher; private @Nullable Callback callback; private ChunkSampleStream[] sampleStreams; private EventSampleStream[] eventSampleStreams; @@ -131,13 +131,6 @@ import java.util.List; */ public void updateManifest(DashManifest manifest, int periodIndex) { this.manifest = manifest; - if (this.periodIndex != periodIndex) { - eventDispatcher = - eventDispatcher.withParameters( - /* windowIndex= */ 0, - eventDispatcher.mediaPeriodId.copyWithPeriodIndex(periodIndex), - manifest.getPeriod(periodIndex).startMs); - } this.periodIndex = periodIndex; playerEmsgHandler.updateManifest(manifest); if (sampleStreams != null) { 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 ec73c3a05e..e9d9c42e0b 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 @@ -631,7 +631,7 @@ public final class DashMediaSource extends BaseMediaSource { @Override public MediaPeriod createPeriod(MediaPeriodId periodId, Allocator allocator) { - int periodIndex = periodId.periodIndex; + int periodIndex = (Integer) periodId.periodUid - firstPeriodId; EventDispatcher periodEventDispatcher = createEventDispatcher(periodId, manifest.getPeriod(periodIndex).startMs); DashMediaPeriod mediaPeriod = diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 670554fe73..24067fcab1 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -425,7 +425,6 @@ public final class HlsMediaSource extends BaseMediaSource @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { - Assertions.checkArgument(id.periodIndex == 0); EventDispatcher eventDispatcher = createEventDispatcher(id); return new HlsMediaPeriod( extractorFactory, diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index dcaecf6d60..efd733d651 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -525,7 +525,6 @@ public final class SsMediaSource extends BaseMediaSource @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { - Assertions.checkArgument(id.periodIndex == 0); EventDispatcher eventDispatcher = createEventDispatcher(id); SsMediaPeriod period = new SsMediaPeriod( diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java index 6510c8b425..e6e4ea53cc 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java @@ -53,7 +53,7 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { Allocator allocator, EventDispatcher eventDispatcher, @Nullable TransferListener transferListener) { - Period period = timeline.getPeriod(id.periodIndex, new Period()); + Period period = timeline.getPeriodByUid(id.periodUid, new Period()); return new FakeAdaptiveMediaPeriod( trackGroupArray, eventDispatcher, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 90e86d6c5a..82d6026482 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -111,8 +111,9 @@ public class FakeMediaSource extends BaseMediaSource { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { assertThat(preparedSource).isTrue(); assertThat(releasedSource).isFalse(); - Assertions.checkIndex(id.periodIndex, 0, timeline.getPeriodCount()); - Period period = timeline.getPeriod(id.periodIndex, new Period()); + int periodIndex = timeline.getIndexOfPeriod(id.periodUid); + Assertions.checkArgument(periodIndex != C.INDEX_UNSET); + Period period = timeline.getPeriod(periodIndex, new Period()); EventDispatcher eventDispatcher = createEventDispatcher(period.windowIndex, id, period.getPositionInWindowMs()); FakeMediaPeriod mediaPeriod = diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 4f9012ab27..70e7669dfb 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -251,12 +251,12 @@ public class MediaSourceTestRunner { public void assertPrepareAndReleaseAllPeriods() throws InterruptedException { Timeline.Period period = new Timeline.Period(); for (int i = 0; i < timeline.getPeriodCount(); i++) { - timeline.getPeriod(i, period); - assertPrepareAndReleasePeriod(new MediaPeriodId(i, period.windowIndex)); + timeline.getPeriod(i, period, /* setIds= */ true); + assertPrepareAndReleasePeriod(new MediaPeriodId(period.uid, period.windowIndex)); for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) { for (int adIndex = 0; adIndex < period.getAdCountInAdGroup(adGroupIndex); adIndex++) { assertPrepareAndReleasePeriod( - new MediaPeriodId(i, adGroupIndex, adIndex, period.windowIndex)); + new MediaPeriodId(period.uid, adGroupIndex, adIndex, period.windowIndex)); } } } @@ -272,7 +272,7 @@ public class MediaSourceTestRunner { // to releasePeriod. MediaPeriodId secondMediaPeriodId = new MediaPeriodId( - mediaPeriodId.periodIndex, + mediaPeriodId.periodUid, mediaPeriodId.adGroupIndex, mediaPeriodId.adIndexInAdGroup, mediaPeriodId.windowSequenceNumber + 1000); @@ -322,8 +322,8 @@ public class MediaSourceTestRunner { int windowIndex = windowIndexAndMediaPeriodId.first; MediaPeriodId mediaPeriodId = windowIndexAndMediaPeriodId.second; if (expectedLoads.remove(mediaPeriodId)) { - assertThat(windowIndex) - .isEqualTo(timeline.getPeriod(mediaPeriodId.periodIndex, period).windowIndex); + int periodIndex = timeline.getIndexOfPeriod(mediaPeriodId.periodUid); + assertThat(windowIndex).isEqualTo(timeline.getPeriod(periodIndex, period).windowIndex); } } assertWithMessage("Not all expected media source loads have been completed.") From 56aecf66147cb3757dc7efd6b0fc77e5abc95fa8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 20 Aug 2018 16:36:25 -0700 Subject: [PATCH 10/42] Make HLS and SS chunk iterators private. They don't need to be accessed publicly and are can be moved into the respective chunk sources. This is the same structure used for Dash. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209507217 --- .../exoplayer2/source/hls/HlsChunkSource.java | 47 +++++++++++++ .../HlsMediaPlaylistSegmentIterator.java | 67 ------------------- .../smoothstreaming/DefaultSsChunkSource.java | 40 +++++++++++ .../smoothstreaming/manifest/SsManifest.java | 42 ------------ 4 files changed, 87 insertions(+), 109 deletions(-) delete mode 100644 library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistSegmentIterator.java diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index ae50c93b83..9b8473ee56 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -22,8 +22,10 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.DataChunk; +import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; @@ -554,4 +556,49 @@ import java.util.List; } + /** {@link MediaChunkIterator} wrapping a {@link HlsMediaPlaylist}. */ + private static final class HlsMediaPlaylistSegmentIterator extends BaseMediaChunkIterator { + + private final HlsMediaPlaylist playlist; + private final long startOfPlaylistInPeriodUs; + + /** + * Creates iterator. + * + * @param playlist The {@link HlsMediaPlaylist} to wrap. + * @param startOfPlaylistInPeriodUs The start time of the playlist in the period, in + * microseconds. + * @param chunkIndex The chunk index in the playlist at which the iterator will start. + */ + public HlsMediaPlaylistSegmentIterator( + HlsMediaPlaylist playlist, long startOfPlaylistInPeriodUs, int chunkIndex) { + super(/* fromIndex= */ chunkIndex, /* toIndex= */ playlist.segments.size() - 1); + this.playlist = playlist; + this.startOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs; + } + + @Override + public DataSpec getDataSpec() { + checkInBounds(); + Segment segment = playlist.segments.get((int) getCurrentIndex()); + Uri chunkUri = UriUtil.resolveToUri(playlist.baseUri, segment.url); + return new DataSpec( + chunkUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null); + } + + @Override + public long getChunkStartTimeUs() { + checkInBounds(); + Segment segment = playlist.segments.get((int) getCurrentIndex()); + return startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; + } + + @Override + public long getChunkEndTimeUs() { + checkInBounds(); + Segment segment = playlist.segments.get((int) getCurrentIndex()); + long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; + return segmentStartTimeInPeriodUs + segment.durationUs; + } + } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistSegmentIterator.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistSegmentIterator.java deleted file mode 100644 index 4c654dc572..0000000000 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistSegmentIterator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.source.hls.playlist; - -import android.net.Uri; -import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; -import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.util.UriUtil; - -/** {@link MediaChunkIterator} wrapping a {@link HlsMediaPlaylist}. */ -public final class HlsMediaPlaylistSegmentIterator extends BaseMediaChunkIterator { - - private final HlsMediaPlaylist playlist; - private final long startOfPlaylistInPeriodUs; - - /** - * Creates iterator. - * - * @param playlist The {@link HlsMediaPlaylist} to wrap. - * @param startOfPlaylistInPeriodUs The start time of the playlist in the period, in microseconds. - * @param chunkIndex The chunk index in the playlist at which the iterator will start. - */ - public HlsMediaPlaylistSegmentIterator( - HlsMediaPlaylist playlist, long startOfPlaylistInPeriodUs, int chunkIndex) { - super(/* fromIndex= */ chunkIndex, /* toIndex= */ playlist.segments.size() - 1); - this.playlist = playlist; - this.startOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs; - } - - @Override - public DataSpec getDataSpec() { - checkInBounds(); - HlsMediaPlaylist.Segment segment = playlist.segments.get((int) getCurrentIndex()); - Uri chunkUri = UriUtil.resolveToUri(playlist.baseUri, segment.url); - return new DataSpec( - chunkUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null); - } - - @Override - public long getChunkStartTimeUs() { - checkInBounds(); - HlsMediaPlaylist.Segment segment = playlist.segments.get((int) getCurrentIndex()); - return startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; - } - - @Override - public long getChunkEndTimeUs() { - checkInBounds(); - HlsMediaPlaylist.Segment segment = playlist.segments.get((int) getCurrentIndex()); - long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; - return segmentStartTimeInPeriodUs + segment.durationUs; - } -} diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index 831d21eeb7..9491298368 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -24,11 +24,13 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.mp4.Track; import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.source.BehindLiveWindowException; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk; +import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -297,4 +299,42 @@ public class DefaultSsChunkSource implements SsChunkSource { return lastChunkEndTimeUs - playbackPositionUs; } + /** {@link MediaChunkIterator} wrapping a track of a {@link StreamElement}. */ + private static final class StreamElementIterator extends BaseMediaChunkIterator { + + private final StreamElement streamElement; + private final int trackIndex; + + /** + * Creates iterator. + * + * @param streamElement The {@link StreamElement} to wrap. + * @param trackIndex The track index in the stream element. + * @param chunkIndex The chunk index at which the iterator will start. + */ + public StreamElementIterator(StreamElement streamElement, int trackIndex, int chunkIndex) { + super(/* fromIndex= */ chunkIndex, /* toIndex= */ streamElement.chunkCount - 1); + this.streamElement = streamElement; + this.trackIndex = trackIndex; + } + + @Override + public DataSpec getDataSpec() { + checkInBounds(); + Uri uri = streamElement.buildRequestUri(trackIndex, (int) getCurrentIndex()); + return new DataSpec(uri); + } + + @Override + public long getChunkStartTimeUs() { + checkInBounds(); + return streamElement.getStartTimeUs((int) getCurrentIndex()); + } + + @Override + public long getChunkEndTimeUs() { + long chunkStartTimeUs = getChunkStartTimeUs(); + return chunkStartTimeUs + streamElement.getChunkDurationUs((int) getCurrentIndex()); + } + } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java index 51284f06c4..2c508f0fde 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java @@ -20,9 +20,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.StreamKey; -import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; -import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; -import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.Util; @@ -51,45 +48,6 @@ public class SsManifest implements FilterableManifest { } } - /** {@link MediaChunkIterator} wrapping a track of a {@link StreamElement}. */ - public static final class StreamElementIterator extends BaseMediaChunkIterator { - - private final StreamElement streamElement; - private final int trackIndex; - - /** - * Creates iterator. - * - * @param streamElement The {@link StreamElement} to wrap. - * @param trackIndex The track index in the stream element. - * @param chunkIndex The chunk index at which the iterator will start. - */ - public StreamElementIterator(StreamElement streamElement, int trackIndex, int chunkIndex) { - super(/* fromIndex= */ chunkIndex, /* toIndex= */ streamElement.chunkCount - 1); - this.streamElement = streamElement; - this.trackIndex = trackIndex; - } - - @Override - public DataSpec getDataSpec() { - checkInBounds(); - Uri uri = streamElement.buildRequestUri(trackIndex, (int) getCurrentIndex()); - return new DataSpec(uri); - } - - @Override - public long getChunkStartTimeUs() { - checkInBounds(); - return streamElement.getStartTimeUs((int) getCurrentIndex()); - } - - @Override - public long getChunkEndTimeUs() { - long chunkStartTimeUs = getChunkStartTimeUs(); - return chunkStartTimeUs + streamElement.getChunkDurationUs((int) getCurrentIndex()); - } - } - /** * Represents a StreamIndex element. */ From bcc69efc7c508de0905f909816fa05e8bc8fb819 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 21 Aug 2018 05:40:31 -0700 Subject: [PATCH 11/42] Remove deprecated SimpleExoPlayer constructor ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209580625 --- .../android/exoplayer2/SimpleExoPlayer.java | 33 ------------------- 1 file changed, 33 deletions(-) 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 65d1113be6..7123e4cc91 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 @@ -108,39 +108,6 @@ public class SimpleExoPlayer private List currentCues; private VideoFrameMetadataListener videoFrameMetadataListener; - /** - * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. - * @param trackSelector The {@link TrackSelector} that will be used by the instance. - * @param loadControl The {@link LoadControl} that will be used by the instance. - * @param bandwidthMeter The {@link BandwidthMeter} that will be used by the instance. - * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance - * will not be used for DRM protected playbacks. - * @param looper The {@link Looper} which must be used for all calls to the player and which is - * used to call listeners on. - * @deprecated Use {@link #SimpleExoPlayer(Context, RenderersFactory, TrackSelector, LoadControl, - * BandwidthMeter, DrmSessionManager, Looper)}. The use of {@link - * SimpleExoPlayer#setAudioAttributes(AudioAttributes, boolean)} to manage audio focus will be - * unavailable for a player created with this constructor. - */ - @Deprecated - protected SimpleExoPlayer( - RenderersFactory renderersFactory, - TrackSelector trackSelector, - LoadControl loadControl, - BandwidthMeter bandwidthMeter, - @Nullable DrmSessionManager drmSessionManager, - Looper looper) { - this( - /* context= */ null, - renderersFactory, - trackSelector, - loadControl, - drmSessionManager, - bandwidthMeter, - new AnalyticsCollector.Factory(), - looper); - } - /** * @param context A {@link Context}. * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. From afebd60ee48f7b22775f0541b8f5c34ab5301c47 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 22 Aug 2018 03:39:57 -0700 Subject: [PATCH 12/42] Do not seek to the start of live Transport Streams after preparation Issue:#4666 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209742008 --- .../android/exoplayer2/extractor/ts/TsExtractor.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index f677dc008f..6234590afa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -243,8 +243,8 @@ public final class TsExtractor implements Extractor { @Override public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { + long inputLength = input.getLength(); if (tracksEnded) { - long inputLength = input.getLength(); boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS; if (canReadDuration && !durationReader.isDurationReadFinished()) { return durationReader.readDuration(input, seekPosition, pcrPid); @@ -324,10 +324,10 @@ public final class TsExtractor implements Extractor { payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); tsPacketBuffer.setLimit(limit); } - if (mode != MODE_HLS && !wereTracksEnded && tracksEnded) { - // We have read all tracks from all PMTs in this stream. Now seek to the beginning and read - // again to make sure we output all media, including any contained in packets prior to those - // containing the track information. + if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) { + // We have read all tracks from all PMTs in this non-live stream. Now seek to the beginning + // and read again to make sure we output all media, including any contained in packets prior + // to those containing the track information. pendingSeekToStart = true; } From 8927993b0315887914c2b20101bebc8ca7614713 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 22 Aug 2018 03:41:18 -0700 Subject: [PATCH 13/42] Prevent a Demo app NPE on Android TV The options menu is not available on Android TV, which triggers a null pointer exception whenever a sample is chosen. This CL is a temporary fix until we rework the UI to not use an options menu. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209742076 --- .../android/exoplayer2/demo/SampleChooserActivity.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 f683e9900f..7dc7890020 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 @@ -22,6 +22,7 @@ import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.Nullable; import android.util.JsonReader; import android.util.Log; import android.view.Menu; @@ -162,8 +163,8 @@ public class SampleChooserActivity extends Activity startActivity( sample.buildIntent( /* context= */ this, - preferExtensionDecodersMenuItem.isChecked(), - randomAbrMenuItem.isChecked() + isNonNullAndChecked(preferExtensionDecodersMenuItem), + isNonNullAndChecked(randomAbrMenuItem) ? PlayerActivity.ABR_ALGORITHM_RANDOM : PlayerActivity.ABR_ALGORITHM_DEFAULT)); return true; @@ -198,6 +199,11 @@ public class SampleChooserActivity extends Activity return 0; } + private static boolean isNonNullAndChecked(@Nullable MenuItem menuItem) { + // Temporary workaround for layouts that do not inflate the options menu. + return menuItem != null && menuItem.isChecked(); + } + private final class SampleListLoader extends AsyncTask> { private boolean sawError; From bf43cca302153e1920198fd3f4986af81f5a072f Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 22 Aug 2018 03:55:24 -0700 Subject: [PATCH 14/42] Fix flaky ExoPlayerTest tests. Some tests were flaky because of the PlayUntilPosition action which called player.setPlayWhenReady from the wrong thread. Also fixed some other misc flakiness. ExoPlayerTest seems to be non-flaky now (tested with 10000 runs). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209743076 --- .../android/exoplayer2/ExoPlayerTest.java | 31 +++++++------------ .../android/exoplayer2/testutil/Action.java | 21 +++++++++++-- 2 files changed, 30 insertions(+), 22 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 6409c1b604..8846e31917 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 @@ -29,7 +29,6 @@ import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; -import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdPlaybackState; @@ -1290,12 +1289,12 @@ public final class ExoPlayerTest { @Test public void testPlaybackErrorDuringSourceInfoRefreshWithShuffleModeEnabledUsesCorrectFirstPeriod() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + FakeMediaSource mediaSource = + new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), /* manifest= */ null); ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource( /* isAtomic= */ false, new FakeShuffleOrder(0), mediaSource, mediaSource); - AtomicInteger windowIndexAfterReprepare = new AtomicInteger(); + AtomicInteger windowIndexAfterError = new AtomicInteger(); ActionSchedule actionSchedule = new ActionSchedule.Builder("testPlaybackErrorDuringSourceInfoRefreshUsesCorrectFirstPeriod") .setShuffleModeEnabled(true) @@ -1304,17 +1303,12 @@ public final class ExoPlayerTest { // is still being prepared. The error will be thrown while the player handles the new // source info. .seek(/* windowIndex= */ 100, /* positionMs= */ 0) - .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) .waitForPlaybackState(Player.STATE_IDLE) - // Re-prepare to play the source in its default shuffled order. - .prepareSource( - concatenatingMediaSource, /* resetPosition= */ false, /* resetState= */ false) - .waitForTimelineChanged(null) .executeRunnable( new PlayerRunnable() { @Override public void run(SimpleExoPlayer player) { - windowIndexAfterReprepare.set(player.getCurrentWindowIndex()); + windowIndexAfterError.set(player.getCurrentWindowIndex()); } }) .build(); @@ -1330,7 +1324,7 @@ public final class ExoPlayerTest { // Expected exception. assertThat(e.getUnexpectedException()).isInstanceOf(IllegalSeekPositionException.class); } - assertThat(windowIndexAfterReprepare.get()).isEqualTo(1); + assertThat(windowIndexAfterError.get()).isEqualTo(1); } @Test @@ -2253,21 +2247,20 @@ public final class ExoPlayerTest { @Test public void testUpdateTrackSelectorThenSeekToUnpreparedPeriod_returnsEmptyTrackGroups() throws Exception { - Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1); + // Use unset duration to prevent pre-loading of the second window. + Timeline fakeTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ C.TIME_UNSET)); MediaSource[] fakeMediaSources = { new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, null, Builder.AUDIO_FORMAT) }; - MediaSource mediaSource = - new ConcatenatingMediaSource( - /* isAtomic= */ false, - /* useLazyPreparation= */ true, - new ShuffleOrder.DefaultShuffleOrder(0), - fakeMediaSources); + MediaSource mediaSource = new ConcatenatingMediaSource(fakeMediaSources); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); DefaultTrackSelector trackSelector = new DefaultTrackSelector(); ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") + new ActionSchedule.Builder("testUpdateTrackSelectorThenSeekToUnpreparedPeriod") .pause() .waitForPlaybackState(Player.STATE_READY) .seek(/* windowIndex= */ 1, /* positionMs= */ 0) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 9e7d583894..f06fcc3add 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; +import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.HandlerWrapper; /** @@ -503,10 +504,24 @@ public abstract class Action { final Surface surface, final HandlerWrapper handler, final ActionNode nextAction) { - // Schedule one message on the playback thread to pause the player immediately. + Handler testThreadHandler = new Handler(); + // Schedule a message on the playback thread to ensure the player is paused immediately. player .createMessage( - (messageType, payload) -> player.setPlayWhenReady(/* playWhenReady= */ false)) + (messageType, payload) -> { + // Block playback thread until pause command has been sent from test thread. + ConditionVariable blockPlaybackThreadCondition = new ConditionVariable(); + testThreadHandler.post( + () -> { + player.setPlayWhenReady(/* playWhenReady= */ false); + blockPlaybackThreadCondition.open(); + }); + try { + blockPlaybackThreadCondition.block(); + } catch (InterruptedException e) { + // Ignore. + } + }) .setPosition(windowIndex, positionMs) .send(); // Schedule another message on this test thread to continue action schedule. @@ -515,7 +530,7 @@ public abstract class Action { (messageType, payload) -> nextAction.schedule(player, trackSelector, surface, handler)) .setPosition(windowIndex, positionMs) - .setHandler(new Handler()) + .setHandler(testThreadHandler) .send(); player.setPlayWhenReady(true); } From 38ea53fcdcefced0b17d06278d7cbea94d5d00cb Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 22 Aug 2018 05:48:27 -0700 Subject: [PATCH 15/42] Revert discarding empty ad breaks The previous change was too aggressive as it would clear future ad breaks. Still clear the pending content position so the real content position is reported after an empty ad break. Issue: #4681 Issue: #4622 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209752306 --- .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index bf1cdfe02c..649e6e386a 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -631,11 +631,8 @@ public final class ImaAdsLoader } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; - int adGroupIndexForPosition = + expectedAdGroupIndex = adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); - if (adGroupIndexForPosition != C.INDEX_UNSET) { - expectedAdGroupIndex = adGroupIndexForPosition; - } } else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) { contentPositionMs = player.getCurrentPosition(); // Update the expected ad group index for the current content position. The update is delayed @@ -1127,15 +1124,6 @@ public final class ImaAdsLoader if (pendingAdLoadError == null) { pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex); } - // Discard the ad break, which makes sure we don't receive duplicate load error events. - adsManager.discardAdBreak(); - // Set the next expected ad group index so we can handle multiple load errors in a row. - adGroupIndex++; - if (adGroupIndex < adPlaybackState.adGroupCount) { - expectedAdGroupIndex = adGroupIndex; - } else { - expectedAdGroupIndex = C.INDEX_UNSET; - } pendingContentPositionMs = C.TIME_UNSET; } From 2b8938533ff766dd58d9a39944b8cb551f1919a8 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Aug 2018 05:52:15 -0700 Subject: [PATCH 16/42] Put all DASH gts URLs in the same place ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209752603 --- .../gts/CommonEncryptionDrmTest.java | 21 ++++++---- .../playbacktests/gts/DashTestData.java | 41 ++++++++++++------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java index 1f8337355b..1832e16a98 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java @@ -36,12 +36,6 @@ public final class CommonEncryptionDrmTest { private static final String TAG = "CencDrmTest"; - private static final String URL_cenc = - "https://storage.googleapis.com/exoplayer-test-media-1/gts/tears-cenc.mpd"; - private static final String URL_cbc1 = - "https://storage.googleapis.com/exoplayer-test-media-1/gts/tears-aes-cbc1.mpd"; - private static final String URL_cbcs = - "https://storage.googleapis.com/exoplayer-test-media-1/gts/tears-aes-cbcs.mpd"; private static final String ID_AUDIO = "0"; private static final String[] IDS_VIDEO = new String[] {"1", "2"}; @@ -76,7 +70,10 @@ public final class CommonEncryptionDrmTest { // Pass. return; } - testRunner.setStreamName("test_widevine_h264_scheme_cenc").setManifestUrl(URL_cenc).run(); + testRunner + .setStreamName("test_widevine_h264_scheme_cenc") + .setManifestUrl(DashTestData.WIDEVINE_SCHEME_CENC) + .run(); } @Test @@ -87,7 +84,10 @@ public final class CommonEncryptionDrmTest { // Pass. return; } - testRunner.setStreamName("test_widevine_h264_scheme_cbc1").setManifestUrl(URL_cbc1).run(); + testRunner + .setStreamName("test_widevine_h264_scheme_cbc1") + .setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBC1) + .run(); } @Test @@ -98,7 +98,10 @@ public final class CommonEncryptionDrmTest { // Pass. return; } - testRunner.setStreamName("test_widevine_h264_scheme_cbcs").setManifestUrl(URL_cbcs).run(); + testRunner + .setStreamName("test_widevine_h264_scheme_cbcs") + .setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBCS) + .run(); } @Test diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java index 33e24aaa13..45cdf34b6c 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java @@ -22,22 +22,35 @@ import com.google.android.exoplayer2.util.Util; */ public final class DashTestData { - private static final String BASE_URL = "https://storage.googleapis.com/exoplayer-test-media-1/" - + "gen-4/screens/dash-vod-single-segment/"; + private static final String BASE_URL = + "https://storage.googleapis.com/exoplayer-test-media-1/gen-4/"; + + private static final String BASE_URL_SCREENS = BASE_URL + "screens/dash-vod-single-segment/"; + private static final String BASE_URL_COMMON_ENCRYPTION = BASE_URL + "common-encryption/"; // Clear content manifests. - public static final String H264_MANIFEST = BASE_URL + "manifest-h264.mpd"; - public static final String H265_MANIFEST = BASE_URL + "manifest-h265.mpd"; - public static final String VP9_MANIFEST = BASE_URL + "manifest-vp9.mpd"; - public static final String H264_23_MANIFEST = BASE_URL + "manifest-h264-23.mpd"; - public static final String H264_24_MANIFEST = BASE_URL + "manifest-h264-24.mpd"; - public static final String H264_29_MANIFEST = BASE_URL + "manifest-h264-29.mpd"; + public static final String H264_MANIFEST = BASE_URL_SCREENS + "manifest-h264.mpd"; + public static final String H265_MANIFEST = BASE_URL_SCREENS + "manifest-h265.mpd"; + public static final String VP9_MANIFEST = BASE_URL_SCREENS + "manifest-vp9.mpd"; + public static final String H264_23_MANIFEST = BASE_URL_SCREENS + "manifest-h264-23.mpd"; + public static final String H264_24_MANIFEST = BASE_URL_SCREENS + "manifest-h264-24.mpd"; + public static final String H264_29_MANIFEST = BASE_URL_SCREENS + "manifest-h264-29.mpd"; // Widevine encrypted content manifests. - public static final String WIDEVINE_H264_MANIFEST = BASE_URL + "manifest-h264-enc.mpd"; - public static final String WIDEVINE_H265_MANIFEST = BASE_URL + "manifest-h265-enc.mpd"; - public static final String WIDEVINE_VP9_MANIFEST = BASE_URL + "manifest-vp9-enc.mpd"; - public static final String WIDEVINE_H264_23_MANIFEST = BASE_URL + "manifest-h264-23-enc.mpd"; - public static final String WIDEVINE_H264_24_MANIFEST = BASE_URL + "manifest-h264-24-enc.mpd"; - public static final String WIDEVINE_H264_29_MANIFEST = BASE_URL + "manifest-h264-29-enc.mpd"; + public static final String WIDEVINE_H264_MANIFEST = BASE_URL_SCREENS + "manifest-h264-enc.mpd"; + public static final String WIDEVINE_H265_MANIFEST = BASE_URL_SCREENS + "manifest-h265-enc.mpd"; + public static final String WIDEVINE_VP9_MANIFEST = BASE_URL_SCREENS + "manifest-vp9-enc.mpd"; + public static final String WIDEVINE_H264_23_MANIFEST = + BASE_URL_SCREENS + "manifest-h264-23-enc.mpd"; + public static final String WIDEVINE_H264_24_MANIFEST = + BASE_URL_SCREENS + "manifest-h264-24-enc.mpd"; + public static final String WIDEVINE_H264_29_MANIFEST = + BASE_URL_SCREENS + "manifest-h264-29-enc.mpd"; + + // Widevine encrypted content manifests using different common encryption schemes. + public static final String WIDEVINE_SCHEME_CENC = BASE_URL_COMMON_ENCRYPTION + "tears-cenc.mpd"; + public static final String WIDEVINE_SCHEME_CBC1 = + BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbc1.mpd"; + public static final String WIDEVINE_SCHEME_CBCS = + BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbcs.mpd"; public static final String AAC_AUDIO_REPRESENTATION_ID = "141"; public static final String H264_BASELINE_240P_VIDEO_REPRESENTATION_ID = "avc-baseline-240"; From 02a8964fe286133bc002b7145c5dd54caab13ffe Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 22 Aug 2018 06:08:55 -0700 Subject: [PATCH 17/42] Support VR180 videos If available parse and use spherical metadata: https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md RELNOTES=true ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209754080 --- RELEASENOTES.md | 4 +- demos/main/src/main/assets/media.exolist.json | 5 + .../exoplayer2/demo/PlayerActivity.java | 2 +- .../video/spherical/Projection.java | 234 ++++++++++++++ .../video/spherical/ProjectionDecoder.java | 233 ++++++++++++++ .../spherical/ProjectionDecoderTest.java | 95 ++++++ .../video/spherical/ProjectionTest.java | 93 ++++++ .../android/exoplayer2/ui/PlayerView.java | 2 + .../android/exoplayer2/ui/spherical/Mesh.java | 290 ------------------ .../ui/spherical/ProjectionRenderer.java | 239 +++++++++++++++ .../ui/spherical/SceneRenderer.java | 44 +-- .../ui/spherical/SphericalSurfaceView.java | 87 ++++-- 12 files changed, 982 insertions(+), 346 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java delete mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/Mesh.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3c05471a89..86cef02a1b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -91,7 +91,9 @@ * Allow setting the `Looper`, which is used to access the player, in `ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)). * Use default Deserializers if non given to DownloadManager. -* Add monoscopic 360 surface type to PlayerView. +* 360: + * Add monoscopic 360 surface type to PlayerView. + * Support VR180 videos. * Deprecate `Player.DefaultEventListener` as selective listener overrides can be directly made with the `Player.EventListener` interface. * Deprecate `DefaultAnalyticsListener` as selective listener overrides can be diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index a366eeba05..732bb5f4f4 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -567,6 +567,11 @@ "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4", "spherical_stereo_mode": "top_bottom" }, + { + "name": "Sphericalv2 (180 top-bottom stereo)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/sphericalv2.mp4", + "spherical_stereo_mode": "top_bottom" + }, { "name": "Iceland (360 top-bottom stereo ts)", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts", diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 3f02023ec5..ffa9bafa4f 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -190,7 +190,7 @@ public class PlayerActivity extends Activity finish(); return; } - ((SphericalSurfaceView) playerView.getVideoSurfaceView()).setStereoMode(stereoMode); + ((SphericalSurfaceView) playerView.getVideoSurfaceView()).setDefaultStereoMode(stereoMode); } if (savedInstanceState != null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java new file mode 100644 index 0000000000..3a585ccd64 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import android.support.annotation.IntDef; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.C.StereoMode; +import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** The projection mesh used with 360/VR videos. */ +public final class Projection { + + /** Enforces allowed (sub) mesh draw modes. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DRAW_MODE_TRIANGLES, DRAW_MODE_TRIANGLES_STRIP, DRAW_MODE_TRIANGLES_FAN}) + public @interface DrawMode {} + /** Triangle draw mode. */ + public static final int DRAW_MODE_TRIANGLES = 0; + /** Triangle strip draw mode. */ + public static final int DRAW_MODE_TRIANGLES_STRIP = 1; + /** Triangle fan draw mode. */ + public static final int DRAW_MODE_TRIANGLES_FAN = 2; + + /** Number of position coordinates per vertex. */ + public static final int TEXTURE_COORDS_PER_VERTEX = 2; + /** Number of texture coordinates per vertex. */ + public static final int POSITION_COORDS_PER_VERTEX = 3; + + /** + * Generates a complete sphere equirectangular projection. + * + * @param stereoMode A {@link C.StereoMode} value. + */ + public static Projection createEquirectangular(@C.StereoMode int stereoMode) { + return createEquirectangular( + /* radius= */ 50, // Should be large enough that there are no stereo artifacts. + /* latitudes= */ 36, // Should be large enough to prevent videos looking wavy. + /* longitudes= */ 72, // Should be large enough to prevent videos looking wavy. + /* verticalFovDegrees= */ 180, + /* horizontalFovDegrees= */ 360, + stereoMode); + } + + /** + * Generates an equirectangular projection. + * + * @param radius Size of the sphere. Must be > 0. + * @param latitudes Number of rows that make up the sphere. Must be >= 1. + * @param longitudes Number of columns that make up the sphere. Must be >= 1. + * @param verticalFovDegrees Total latitudinal degrees that are covered by the sphere. Must be in + * (0, 180]. + * @param horizontalFovDegrees Total longitudinal degrees that are covered by the sphere.Must be + * in (0, 360]. + * @param stereoMode A {@link C.StereoMode} value. + * @return an equirectangular projection. + */ + public static Projection createEquirectangular( + float radius, + int latitudes, + int longitudes, + float verticalFovDegrees, + float horizontalFovDegrees, + @C.StereoMode int stereoMode) { + Assertions.checkArgument(radius > 0); + Assertions.checkArgument(latitudes >= 1); + Assertions.checkArgument(longitudes >= 1); + Assertions.checkArgument(verticalFovDegrees > 0 && verticalFovDegrees <= 180); + Assertions.checkArgument(horizontalFovDegrees > 0 && horizontalFovDegrees <= 360); + + // Compute angular size in radians of each UV quad. + float verticalFovRads = (float) Math.toRadians(verticalFovDegrees); + float horizontalFovRads = (float) Math.toRadians(horizontalFovDegrees); + float quadHeightRads = verticalFovRads / latitudes; + float quadWidthRads = horizontalFovRads / longitudes; + + // Each latitude strip has 2 * (longitudes quads + extra edge) vertices + 2 degenerate vertices. + int vertexCount = (2 * (longitudes + 1) + 2) * latitudes; + // Buffer to return. + float[] vertexData = new float[vertexCount * POSITION_COORDS_PER_VERTEX]; + float[] textureData = new float[vertexCount * TEXTURE_COORDS_PER_VERTEX]; + + // Generate the data for the sphere which is a set of triangle strips representing each + // latitude band. + int vOffset = 0; // Offset into the vertexData array. + int tOffset = 0; // Offset into the textureData array. + // (i, j) represents a quad in the equirectangular sphere. + for (int j = 0; j < latitudes; ++j) { // For each horizontal triangle strip. + // Each latitude band lies between the two phi values. Each vertical edge on a band lies on + // a theta value. + float phiLow = quadHeightRads * j - verticalFovRads / 2; + float phiHigh = quadHeightRads * (j + 1) - verticalFovRads / 2; + + for (int i = 0; i < longitudes + 1; ++i) { // For each vertical edge in the band. + for (int k = 0; k < 2; ++k) { // For low and high points on an edge. + // For each point, determine it's position in polar coordinates. + float phi = k == 0 ? phiLow : phiHigh; + float theta = quadWidthRads * i + (float) Math.PI - horizontalFovRads / 2; + + // Set vertex position data as Cartesian coordinates. + vertexData[vOffset++] = -(float) (radius * Math.sin(theta) * Math.cos(phi)); + vertexData[vOffset++] = (float) (radius * Math.sin(phi)); + vertexData[vOffset++] = (float) (radius * Math.cos(theta) * Math.cos(phi)); + + textureData[tOffset++] = i * quadWidthRads / horizontalFovRads; + textureData[tOffset++] = (j + k) * quadHeightRads / verticalFovRads; + + // Break up the triangle strip with degenerate vertices by copying first and last points. + if ((i == 0 && k == 0) || (i == longitudes && k == 1)) { + System.arraycopy( + vertexData, + vOffset - POSITION_COORDS_PER_VERTEX, + vertexData, + vOffset, + POSITION_COORDS_PER_VERTEX); + vOffset += POSITION_COORDS_PER_VERTEX; + System.arraycopy( + textureData, + tOffset - TEXTURE_COORDS_PER_VERTEX, + textureData, + tOffset, + TEXTURE_COORDS_PER_VERTEX); + tOffset += TEXTURE_COORDS_PER_VERTEX; + } + } + // Move on to the next vertical edge in the triangle strip. + } + // Move on to the next triangle strip. + } + SubMesh subMesh = + new SubMesh(SubMesh.VIDEO_TEXTURE_ID, vertexData, textureData, DRAW_MODE_TRIANGLES_STRIP); + return new Projection(new Mesh(subMesh), stereoMode); + } + + /** The Mesh corresponding to the left eye. */ + public final Mesh leftMesh; + /** + * The Mesh corresponding to the right eye. If {@code singleMesh} is true then this mesh is + * identical to {@link #leftMesh}. + */ + public final Mesh rightMesh; + /** The stereo mode. */ + public final @StereoMode int stereoMode; + /** Whether the left and right mesh are identical. */ + public final boolean singleMesh; + + /** + * Creates a Projection with single mesh. + * + * @param mesh the Mesh for both eyes. + * @param stereoMode A {@link StereoMode} value. + */ + public Projection(Mesh mesh, int stereoMode) { + this(mesh, mesh, stereoMode); + } + + /** + * Creates a Projection with dual mesh. Use {@link #Projection(Mesh, int)} if there is single mesh + * for both eyes. + * + * @param leftMesh the Mesh corresponding to the left eye. + * @param rightMesh the Mesh corresponding to the right eye. + * @param stereoMode A {@link C.StereoMode} value. + */ + public Projection(Mesh leftMesh, Mesh rightMesh, int stereoMode) { + this.leftMesh = leftMesh; + this.rightMesh = rightMesh; + this.stereoMode = stereoMode; + this.singleMesh = leftMesh == rightMesh; + } + + /** The sub mesh associated with the {@link Mesh}. */ + public static final class SubMesh { + /** Texture ID for video frames. */ + public static final int VIDEO_TEXTURE_ID = 0; + + /** Texture ID. */ + public final int textureId; + /** The drawing mode. One of {@link DrawMode}. */ + public final @DrawMode int mode; + /** The SubMesh vertices. */ + public final float[] vertices; + /** The SubMesh texture coordinates. */ + public final float[] textureCoords; + + public SubMesh(int textureId, float[] vertices, float[] textureCoords, @DrawMode int mode) { + this.textureId = textureId; + Assertions.checkArgument( + vertices.length * (long) TEXTURE_COORDS_PER_VERTEX + == textureCoords.length * (long) POSITION_COORDS_PER_VERTEX); + this.vertices = vertices; + this.textureCoords = textureCoords; + this.mode = mode; + } + + /** Returns the SubMesh vertex count. */ + public int getVertexCount() { + return vertices.length / POSITION_COORDS_PER_VERTEX; + } + } + + /** A Mesh associated with the projection scene. */ + public static final class Mesh { + private final SubMesh[] subMeshes; + + public Mesh(SubMesh... subMeshes) { + this.subMeshes = subMeshes; + } + + /** Returns the number of sub meshes. */ + public int getSubMeshCount() { + return subMeshes.length; + } + + /** Returns the SubMesh for the given index. */ + public SubMesh getSubMesh(int index) { + return subMeshes[index]; + } + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java new file mode 100644 index 0000000000..4ef87bddfb --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.spherical.Projection.Mesh; +import com.google.android.exoplayer2.video.spherical.Projection.SubMesh; +import java.util.ArrayList; +import java.util.zip.Inflater; + +/** + * A decoder for the projection mesh. + * + *

The mesh boxes parsed are described at + * Spherical Video V2 RFC. + * + *

The decoder does not perform CRC checks at the moment. + */ +public final class ProjectionDecoder { + + private static final int TYPE_YTMP = Util.getIntegerCodeForString("ytmp"); + private static final int TYPE_MSHP = Util.getIntegerCodeForString("mshp"); + private static final int TYPE_RAW = Util.getIntegerCodeForString("raw "); + private static final int TYPE_DFL8 = Util.getIntegerCodeForString("dfl8"); + private static final int TYPE_MESH = Util.getIntegerCodeForString("mesh"); + private static final int TYPE_PROJ = Util.getIntegerCodeForString("proj"); + + // Sanity limits to prevent a bad file from creating an OOM situation. We don't expect a mesh to + // exceed these limits. + private static final int MAX_COORDINATE_COUNT = 10000; + private static final int MAX_VERTEX_COUNT = 32 * 1000; + private static final int MAX_TRIANGLE_INDICES = 128 * 1000; + + private ProjectionDecoder() {} + + /* + * Decodes the projection data. + * + * @param projectionData The projection data. + * @param stereoMode A {@link C.StereoMode} value. + * @return The projection or null if the data can't be decoded. + */ + public static @Nullable Projection decode(byte[] projectionData, @C.StereoMode int stereoMode) { + ParsableByteArray input = new ParsableByteArray(projectionData); + // MP4 containers include the proj box but webm containers do not. + // Both containers use mshp. + ArrayList meshes = null; + try { + meshes = isProj(input) ? parseProj(input) : parseMshp(input); + } catch (ArrayIndexOutOfBoundsException ignored) { + // Do nothing. + } + if (meshes == null) { + return null; + } else { + switch (meshes.size()) { + case 1: + return new Projection(meshes.get(0), stereoMode); + case 2: + return new Projection(meshes.get(0), meshes.get(1), stereoMode); + case 0: + default: + return null; + } + } + } + + /** Returns true if the input contains a proj box. Indicates MP4 container. */ + private static boolean isProj(ParsableByteArray input) { + input.skipBytes(4); // size + int type = input.readInt(); + input.setPosition(0); + return type == TYPE_PROJ; + } + + private static @Nullable ArrayList parseProj(ParsableByteArray input) { + input.skipBytes(8); // size and type. + int position = input.getPosition(); + int limit = input.limit(); + while (position < limit) { + int childEnd = position + input.readInt(); + if (childEnd <= position || childEnd > limit) { + return null; + } + int childAtomType = input.readInt(); + // Some early files named the atom ytmp rather than mshp. + if (childAtomType == TYPE_YTMP || childAtomType == TYPE_MSHP) { + input.setLimit(childEnd); + return parseMshp(input); + } + position = childEnd; + input.setPosition(position); + } + return null; + } + + private static @Nullable ArrayList parseMshp(ParsableByteArray input) { + int version = input.readUnsignedByte(); + if (version != 0) { + return null; + } + input.skipBytes(7); // flags + crc. + int encoding = input.readInt(); + if (encoding == TYPE_DFL8) { + ParsableByteArray output = new ParsableByteArray(); + if (!Util.inflate(input, output, new Inflater(true))) { + return null; + } + input = output; + } else if (encoding != TYPE_RAW) { + return null; + } + return parseRawMshpData(input); + } + + /** Parses MSHP data after the encoding_four_cc field. */ + private static @Nullable ArrayList parseRawMshpData(ParsableByteArray input) { + ArrayList meshes = new ArrayList<>(); + int position = input.getPosition(); + int limit = input.limit(); + while (position < limit) { + int childEnd = position + input.readInt(); + if (childEnd <= position || childEnd > limit) { + return null; + } + int childAtomType = input.readInt(); + if (childAtomType == TYPE_MESH) { + Mesh mesh = parseMesh(input); + if (mesh == null) { + return null; + } + meshes.add(mesh); + } + position = childEnd; + input.setPosition(position); + } + return meshes; + } + + private static @Nullable Mesh parseMesh(ParsableByteArray input) { + // Read the coordinates. + int coordinateCount = input.readInt(); + if (coordinateCount > MAX_COORDINATE_COUNT) { + return null; + } + float[] coordinates = new float[coordinateCount]; + for (int coordinate = 0; coordinate < coordinateCount; coordinate++) { + coordinates[coordinate] = input.readFloat(); + } + // Read the vertices. + int vertexCount = input.readInt(); + if (vertexCount > MAX_VERTEX_COUNT) { + return null; + } + + final double log2 = Math.log(2.0); + int coordinateCountSizeBits = (int) Math.ceil(Math.log(2.0 * coordinateCount) / log2); + + ParsableBitArray bitInput = new ParsableBitArray(input.data); + bitInput.setPosition(input.getPosition() * 8); + float[] vertices = new float[vertexCount * 5]; + int[] coordinateIndices = new int[5]; + int vertexIndex = 0; + for (int vertex = 0; vertex < vertexCount; vertex++) { + for (int i = 0; i < 5; i++) { + int coordinateIndex = + coordinateIndices[i] + decodeZigZag(bitInput.readBits(coordinateCountSizeBits)); + if (coordinateIndex >= coordinateCount || coordinateIndex < 0) { + return null; + } + vertices[vertexIndex++] = coordinates[coordinateIndex]; + coordinateIndices[i] = coordinateIndex; + } + } + + // Pad to next byte boundary + bitInput.setPosition(((bitInput.getPosition() + 7) & ~7)); + + int subMeshCount = bitInput.readBits(32); + SubMesh[] subMeshes = new SubMesh[subMeshCount]; + for (int i = 0; i < subMeshCount; i++) { + int textureId = bitInput.readBits(8); + int drawMode = bitInput.readBits(8); + int triangleIndexCount = bitInput.readBits(32); + if (triangleIndexCount > MAX_TRIANGLE_INDICES) { + return null; + } + int vertexCountSizeBits = (int) Math.ceil(Math.log(2.0 * vertexCount) / log2); + int index = 0; + float[] triangleVertices = new float[triangleIndexCount * 3]; + float[] textureCoords = new float[triangleIndexCount * 2]; + for (int counter = 0; counter < triangleIndexCount; counter++) { + index += decodeZigZag(bitInput.readBits(vertexCountSizeBits)); + if (index < 0 || index >= vertexCount) { + return null; + } + triangleVertices[counter * 3] = vertices[index * 5]; + triangleVertices[counter * 3 + 1] = vertices[index * 5 + 1]; + triangleVertices[counter * 3 + 2] = vertices[index * 5 + 2]; + textureCoords[counter * 2] = vertices[index * 5 + 3]; + textureCoords[counter * 2 + 1] = vertices[index * 5 + 4]; + } + subMeshes[i] = new SubMesh(textureId, triangleVertices, textureCoords, drawMode); + } + return new Mesh(subMeshes); + } + + /** + * Decodes Zigzag encoding as described in + * https://developers.google.com/protocol-buffers/docs/encoding#signed-integers + */ + private static int decodeZigZag(int n) { + return (n >> 1) ^ -(n & 1); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java new file mode 100644 index 0000000000..af1a8421b4 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Util; +import java.util.Arrays; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link ProjectionDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public final class ProjectionDecoderTest { + + private static final byte[] PROJ_DATA = + Util.getBytesFromHexString( + "0000008D70726F6A0000008579746D7000000000ABA158D672617720000000716D65736800000006BF800000" + + "3F8000003F0000003F2AAAAB000000003EAAAAAB000000100024200104022430010421034020400123" + + "1020401013020010102222001001003100200010320010000000010000000000240084009066080420" + + "9020108421002410860214C1200660"); + + private static final int MSHP_OFFSET = 16; + private static final int VERTEX_COUNT = 36; + private static final float[] FIRST_VERTEX = {-1.0f, -1.0f, 1.0f}; + private static final float[] LAST_VERTEX = {1.0f, -1.0f, -1.0f}; + private static final float[] FIRST_UV = {0.5f, 1.0f}; + private static final float[] LAST_UV = {1.0f, 1.0f}; + + @Test + public void testDecodeProj() { + testDecoding(PROJ_DATA); + } + + @Test + public void testDecodeMshp() { + testDecoding(Arrays.copyOfRange(PROJ_DATA, MSHP_OFFSET, PROJ_DATA.length)); + } + + private static void testDecoding(byte[] data) { + Projection projection = ProjectionDecoder.decode(data, C.STEREO_MODE_MONO); + assertThat(projection).isNotNull(); + assertThat(projection.stereoMode).isEqualTo(C.STEREO_MODE_MONO); + assertThat(projection.leftMesh).isNotNull(); + assertThat(projection.rightMesh).isNotNull(); + assertThat(projection.singleMesh).isTrue(); + testSubMesh(projection.leftMesh); + } + + /** Tests the that SubMesh (mesh with the video) contains expected data. */ + private static void testSubMesh(Projection.Mesh leftMesh) { + assertThat(leftMesh.getSubMeshCount()).isEqualTo(1); + + Projection.SubMesh subMesh = leftMesh.getSubMesh(0); + assertThat(subMesh.mode).isEqualTo(Projection.DRAW_MODE_TRIANGLES); + + float[] vertices = subMesh.vertices; + float[] uv = subMesh.textureCoords; + assertThat(vertices.length).isEqualTo(VERTEX_COUNT * 3); + assertThat(subMesh.textureCoords.length).isEqualTo(VERTEX_COUNT * 2); + + // Test first vertex + testCoordinate(FIRST_VERTEX, vertices, 0, 3); + // Test last vertex + testCoordinate(LAST_VERTEX, vertices, VERTEX_COUNT * 3 - 3, 3); + + // Test first uv + testCoordinate(FIRST_UV, uv, 0, 2); + // Test last uv + testCoordinate(LAST_UV, uv, VERTEX_COUNT * 2 - 2, 2); + } + + /** Tests that the output coordinates match the expected. */ + private static void testCoordinate(float[] expected, float[] output, int offset, int count) { + for (int i = 0; i < count; i++) { + Assert.assertEquals(expected[i], output[i + offset]); + } + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java new file mode 100644 index 0000000000..0e2d0999fb --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.google.android.exoplayer2.C; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link Projection}. */ +@RunWith(RobolectricTestRunner.class) +public class ProjectionTest { + private static final float EPSILON = .00001f; + + // Default 360 sphere. + private static final float RADIUS = 1; + private static final int LATITUDES = 12; + private static final int LONGITUDES = 24; + private static final float VERTICAL_FOV_DEGREES = 180; + private static final float HORIZONTAL_FOV_DEGREES = 360; + + @Test + public void testSphericalMesh() throws Exception { + // Only the first param is important in this test. + Projection projection = + Projection.createEquirectangular( + RADIUS, + LATITUDES, + LONGITUDES, + VERTICAL_FOV_DEGREES, + HORIZONTAL_FOV_DEGREES, + C.STEREO_MODE_MONO); + + Projection.SubMesh subMesh = projection.leftMesh.getSubMesh(0); + assertThat(subMesh.getVertexCount()).isGreaterThan(LATITUDES * LONGITUDES); + + float[] data = subMesh.vertices; + for (int i = 0; i < data.length; ) { + float x = data[i++]; + float y = data[i++]; + float z = data[i++]; + assertEquals(RADIUS, Math.sqrt(x * x + y * y + z * z), EPSILON); + } + } + + @Test + public void testArgumentValidation() { + checkIllegalArgumentException(0, 1, 1, 1, 1); + checkIllegalArgumentException(1, 0, 1, 1, 1); + checkIllegalArgumentException(1, 1, 0, 1, 1); + checkIllegalArgumentException(1, 1, 1, 0, 1); + checkIllegalArgumentException(1, 1, 1, 181, 1); + checkIllegalArgumentException(1, 1, 1, 1, 0); + checkIllegalArgumentException(1, 1, 1, 1, 361); + } + + private void checkIllegalArgumentException( + float radius, + int latitudes, + int longitudes, + float verticalFovDegrees, + float horizontalFovDegrees) { + try { + Projection.createEquirectangular( + radius, + latitudes, + longitudes, + verticalFovDegrees, + horizontalFovDegrees, + C.STEREO_MODE_MONO); + fail(); + } catch (IllegalArgumentException e) { + // Do nothing. Expected. + } + } +} 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 99f38b4c40..e7c43f234f 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 @@ -509,6 +509,7 @@ public class PlayerView extends FrameLayout { oldVideoComponent.clearVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SphericalSurfaceView) { oldVideoComponent.clearVideoSurface(((SphericalSurfaceView) surfaceView).getSurface()); + oldVideoComponent.clearVideoFrameMetadataListener(((SphericalSurfaceView) surfaceView)); } else if (surfaceView instanceof SurfaceView) { oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView); } @@ -535,6 +536,7 @@ public class PlayerView extends FrameLayout { newVideoComponent.setVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SphericalSurfaceView) { newVideoComponent.setVideoSurface(((SphericalSurfaceView) surfaceView).getSurface()); + newVideoComponent.setVideoFrameMetadataListener(((SphericalSurfaceView) surfaceView)); } else if (surfaceView instanceof SurfaceView) { newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/Mesh.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/Mesh.java deleted file mode 100644 index d3d7d854ae..0000000000 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/Mesh.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui.spherical; - -import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; - -import android.annotation.TargetApi; -import android.opengl.GLES11Ext; -import android.opengl.GLES20; -import com.google.android.exoplayer2.C; -import java.nio.FloatBuffer; - -/** - * Utility class to generate & render spherical meshes for video or images. Use the static creation - * methods to construct the Mesh's data. Then call the Mesh constructor on the GL thread when ready. - * Use glDraw method to render it. - */ -@TargetApi(15) -/*package*/ final class Mesh { - - /** Defines the constants identifying the current eye type. */ - /*package*/ interface EyeType { - /** Single eye in monocular rendering. */ - int MONOCULAR = 0; - - /** The left eye in stereo rendering. */ - int LEFT = 1; - - /** The right eye in stereo rendering. */ - int RIGHT = 2; - } - - // Basic vertex & fragment shaders to render a mesh with 3D position & 2D texture data. - private static final String[] VERTEX_SHADER_CODE = - new String[] { - "uniform mat4 uMvpMatrix;", - "attribute vec4 aPosition;", - "attribute vec2 aTexCoords;", - "varying vec2 vTexCoords;", - - // Standard transformation. - "void main() {", - " gl_Position = uMvpMatrix * aPosition;", - " vTexCoords = aTexCoords;", - "}" - }; - private static final String[] FRAGMENT_SHADER_CODE = - new String[] { - // This is required since the texture data is GL_TEXTURE_EXTERNAL_OES. - "#extension GL_OES_EGL_image_external : require", - "precision mediump float;", - - // Standard texture rendering shader. - "uniform samplerExternalOES uTexture;", - "varying vec2 vTexCoords;", - "void main() {", - " gl_FragColor = texture2D(uTexture, vTexCoords);", - "}" - }; - - // Constants related to vertex data. - private static final int POSITION_COORDS_PER_VERTEX = 3; // X, Y, Z. - // The vertex contains texture coordinates for both the left & right eyes. If the scene is - // rendered in VR, the appropriate part of the vertex will be selected at runtime. For a mono - // scene, only the left eye's UV coordinates are used. - // For mono media, the UV coordinates are duplicated in each. For stereo media, the UV coords - // point to the appropriate part of the source media. - private static final int TEXTURE_COORDS_PER_VERTEX = 2 * 2; - private static final int COORDS_PER_VERTEX = - POSITION_COORDS_PER_VERTEX + TEXTURE_COORDS_PER_VERTEX; - // Data is tightly packed. Each vertex is [x, y, z, u_left, v_left, u_right, v_right]. - private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * C.BYTES_PER_FLOAT; - - // Vertices for the mesh with 3D position + left 2D texture UV + right 2D texture UV. - private final int vertixCount; - private final FloatBuffer vertexBuffer; - - // Program related GL items. These are only valid if program != 0. - private int program; - private int mvpMatrixHandle; - private int positionHandle; - private int texCoordsHandle; - private int textureHandle; - - /** - * Generates a 3D UV sphere for rendering monoscopic or stereoscopic video. - * - *

This can be called on any thread. The returned {@link Mesh} isn't valid until {@link - * #init()} is called. - * - * @param radius Size of the sphere. Must be > 0. - * @param latitudes Number of rows that make up the sphere. Must be >= 1. - * @param longitudes Number of columns that make up the sphere. Must be >= 1. - * @param verticalFovDegrees Total latitudinal degrees that are covered by the sphere. Must be in - * (0, 180]. - * @param horizontalFovDegrees Total longitudinal degrees that are covered by the sphere.Must be - * in (0, 360]. - * @param stereoMode A {@link C.StereoMode} value. - * @return Unintialized Mesh. - */ - public static Mesh createUvSphere( - float radius, - int latitudes, - int longitudes, - float verticalFovDegrees, - float horizontalFovDegrees, - @C.StereoMode int stereoMode) { - return new Mesh( - createUvSphereVertexData( - radius, latitudes, longitudes, verticalFovDegrees, horizontalFovDegrees, stereoMode)); - } - - /** Used by static constructors. */ - private Mesh(float[] vertexData) { - vertixCount = vertexData.length / COORDS_PER_VERTEX; - vertexBuffer = GlUtil.createBuffer(vertexData); - } - - /** Initializes of the GL components. */ - /* package */ void init() { - program = GlUtil.compileProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE); - mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMvpMatrix"); - positionHandle = GLES20.glGetAttribLocation(program, "aPosition"); - texCoordsHandle = GLES20.glGetAttribLocation(program, "aTexCoords"); - textureHandle = GLES20.glGetUniformLocation(program, "uTexture"); - } - - /** - * Renders the mesh. This must be called on the GL thread. - * - * @param textureId GL_TEXTURE_EXTERNAL_OES used for this mesh. - * @param mvpMatrix The Model View Projection matrix. - * @param eyeType An {@link EyeType} value. - */ - /* package */ void draw(int textureId, float[] mvpMatrix, int eyeType) { - // Configure shader. - GLES20.glUseProgram(program); - checkGlError(); - - GLES20.glEnableVertexAttribArray(positionHandle); - GLES20.glEnableVertexAttribArray(texCoordsHandle); - checkGlError(); - - GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0); - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); - GLES20.glUniform1i(textureHandle, 0); - checkGlError(); - - // Load position data. - vertexBuffer.position(0); - GLES20.glVertexAttribPointer( - positionHandle, - POSITION_COORDS_PER_VERTEX, - GLES20.GL_FLOAT, - false, - VERTEX_STRIDE_BYTES, - vertexBuffer); - checkGlError(); - - // Load texture data. Eye.Type.RIGHT uses the left eye's data. - int textureOffset = - (eyeType == EyeType.RIGHT) ? POSITION_COORDS_PER_VERTEX + 2 : POSITION_COORDS_PER_VERTEX; - vertexBuffer.position(textureOffset); - GLES20.glVertexAttribPointer( - texCoordsHandle, - TEXTURE_COORDS_PER_VERTEX, - GLES20.GL_FLOAT, - false, - VERTEX_STRIDE_BYTES, - vertexBuffer); - checkGlError(); - - // Render. - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertixCount); - checkGlError(); - - GLES20.glDisableVertexAttribArray(positionHandle); - GLES20.glDisableVertexAttribArray(texCoordsHandle); - } - - /** Cleans up the GL resources. */ - /* package */ void shutdown() { - if (program != 0) { - GLES20.glDeleteProgram(program); - } - } - - // @VisibleForTesting - /*package*/ static float[] createUvSphereVertexData( - float radius, - int latitudes, - int longitudes, - float verticalFovDegrees, - float horizontalFovDegrees, - @C.StereoMode int stereoMode) { - if (radius <= 0 - || latitudes < 1 - || longitudes < 1 - || verticalFovDegrees <= 0 - || verticalFovDegrees > 180 - || horizontalFovDegrees <= 0 - || horizontalFovDegrees > 360) { - throw new IllegalArgumentException("Invalid parameters for sphere."); - } - - // Compute angular size in radians of each UV quad. - float verticalFovRads = (float) Math.toRadians(verticalFovDegrees); - float horizontalFovRads = (float) Math.toRadians(horizontalFovDegrees); - float quadHeightRads = verticalFovRads / latitudes; - float quadWidthRads = horizontalFovRads / longitudes; - - // Each latitude strip has 2 * (longitudes quads + extra edge) vertices + 2 degenerate vertices. - int vertexCount = (2 * (longitudes + 1) + 2) * latitudes; - // Buffer to return. - float[] vertexData = new float[vertexCount * COORDS_PER_VERTEX]; - - // Generate the data for the sphere which is a set of triangle strips representing each - // latitude band. - int offset = 0; // Offset into the vertexData array. - // (i, j) represents a quad in the equirectangular sphere. - for (int j = 0; j < latitudes; ++j) { // For each horizontal triangle strip. - // Each latitude band lies between the two phi values. Each vertical edge on a band lies on - // a theta value. - float phiLow = (quadHeightRads * j - verticalFovRads / 2); - float phiHigh = (quadHeightRads * (j + 1) - verticalFovRads / 2); - - for (int i = 0; i < longitudes + 1; ++i) { // For each vertical edge in the band. - for (int k = 0; k < 2; ++k) { // For low and high points on an edge. - // For each point, determine it's position in polar coordinates. - float phi = (k == 0) ? phiLow : phiHigh; - float theta = quadWidthRads * i + (float) Math.PI - horizontalFovRads / 2; - - // Set vertex position data as Cartesian coordinates. - vertexData[offset] = -(float) (radius * Math.sin(theta) * Math.cos(phi)); - vertexData[offset + 1] = (float) (radius * Math.sin(phi)); - vertexData[offset + 2] = (float) (radius * Math.cos(theta) * Math.cos(phi)); - - // Set vertex texture.x data. - if (stereoMode == C.STEREO_MODE_LEFT_RIGHT) { - // For left-right media, each eye's x coordinate points to the left or right half of the - // texture. - vertexData[offset + 3] = (i * quadWidthRads / horizontalFovRads) / 2; - vertexData[offset + 5] = (i * quadWidthRads / horizontalFovRads) / 2 + .5f; - } else { - // For top-bottom or monoscopic media, the eye's x spans the full width of the texture. - vertexData[offset + 3] = i * quadWidthRads / horizontalFovRads; - vertexData[offset + 5] = i * quadWidthRads / horizontalFovRads; - } - - // Set vertex texture.y data. The "1 - ..." is due to Canvas vs GL coords. - if (stereoMode == C.STEREO_MODE_TOP_BOTTOM) { - // For top-bottom media, each eye's y coordinate points to the top or bottom half of the - // texture. - vertexData[offset + 4] = 1 - (((j + k) * quadHeightRads / verticalFovRads) / 2 + .5f); - vertexData[offset + 6] = 1 - ((j + k) * quadHeightRads / verticalFovRads) / 2; - } else { - // For left-right or monoscopic media, the eye's y spans the full height of the texture. - vertexData[offset + 4] = 1 - (j + k) * quadHeightRads / verticalFovRads; - vertexData[offset + 6] = 1 - (j + k) * quadHeightRads / verticalFovRads; - } - offset += COORDS_PER_VERTEX; - - // Break up the triangle strip with degenerate vertices by copying first and last points. - if ((i == 0 && k == 0) || (i == longitudes && k == 1)) { - System.arraycopy( - vertexData, offset - COORDS_PER_VERTEX, vertexData, offset, COORDS_PER_VERTEX); - offset += COORDS_PER_VERTEX; - } - } - // Move on to the next vertical edge in the triangle strip. - } - // Move on to the next triangle strip. - } - return vertexData; - } -} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java new file mode 100644 index 0000000000..3b3e921253 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui.spherical; + +import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; + +import android.annotation.TargetApi; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.video.spherical.Projection; +import java.nio.FloatBuffer; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Utility class to render spherical meshes for video or images. Call {@link #init()} on the GL + * thread when ready. + */ +@TargetApi(15) +/*package*/ final class ProjectionRenderer { + + /** Defines the constants identifying the current eye type. */ + /*package*/ interface EyeType { + /** Single eye in monocular rendering. */ + int MONOCULAR = 0; + + /** The left eye in stereo rendering. */ + int LEFT = 1; + + /** The right eye in stereo rendering. */ + int RIGHT = 2; + } + + /** + * Returns whether {@code projection} is supported. At least it should have left mesh and there + * should be only one sub mesh per mesh. + */ + public static boolean isSupported(Projection projection) { + Projection.Mesh leftMesh = projection.leftMesh; + Projection.Mesh rightMesh = projection.rightMesh; + return leftMesh.getSubMeshCount() == 1 + && leftMesh.getSubMesh(0).textureId == Projection.SubMesh.VIDEO_TEXTURE_ID + && rightMesh.getSubMeshCount() == 1 + && rightMesh.getSubMesh(0).textureId == Projection.SubMesh.VIDEO_TEXTURE_ID; + } + + // Basic vertex & fragment shaders to render a mesh with 3D position & 2D texture data. + private static final String[] VERTEX_SHADER_CODE = + new String[] { + "uniform mat4 uMvpMatrix;", + "uniform mat3 uTexMatrix;", + "attribute vec4 aPosition;", + "attribute vec2 aTexCoords;", + "varying vec2 vTexCoords;", + + // Standard transformation. + "void main() {", + " gl_Position = uMvpMatrix * aPosition;", + " vTexCoords = (uTexMatrix * vec3(aTexCoords, 1)).xy;", + "}" + }; + private static final String[] FRAGMENT_SHADER_CODE = + new String[] { + // This is required since the texture data is GL_TEXTURE_EXTERNAL_OES. + "#extension GL_OES_EGL_image_external : require", + "precision mediump float;", + + // Standard texture rendering shader. + "uniform samplerExternalOES uTexture;", + "varying vec2 vTexCoords;", + "void main() {", + " gl_FragColor = texture2D(uTexture, vTexCoords);", + "}" + }; + + // Texture transform matrices. + private static final float[] TEX_MATRIX_WHOLE = { + 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f + }; + private static final float[] TEX_MATRIX_TOP = { + 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.5f, 1.0f + }; + private static final float[] TEX_MATRIX_BOTTOM = { + 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f + }; + private static final float[] TEX_MATRIX_LEFT = { + 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f + }; + private static final float[] TEX_MATRIX_RIGHT = { + 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.5f, 1.0f, 1.0f + }; + + private int stereoMode; + private @Nullable MeshData leftMeshData; + private @Nullable MeshData rightMeshData; + + // Program related GL items. These are only valid if program != 0. + private int program; + private int mvpMatrixHandle; + private int uTexMatrixHandle; + private int positionHandle; + private int texCoordsHandle; + private int textureHandle; + + /** + * Sets a {@link Projection} to be used. + * + * @param projection Contains the projection data to be rendered. + * @see #isSupported(Projection) + */ + public void setProjection(Projection projection) { + if (!isSupported(projection)) { + return; + } + stereoMode = projection.stereoMode; + leftMeshData = new MeshData(projection.leftMesh.getSubMesh(0)); + rightMeshData = + projection.singleMesh ? leftMeshData : new MeshData(projection.rightMesh.getSubMesh(0)); + } + + /** Initializes of the GL components. */ + /* package */ void init() { + program = GlUtil.compileProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE); + mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMvpMatrix"); + uTexMatrixHandle = GLES20.glGetUniformLocation(program, "uTexMatrix"); + positionHandle = GLES20.glGetAttribLocation(program, "aPosition"); + texCoordsHandle = GLES20.glGetAttribLocation(program, "aTexCoords"); + textureHandle = GLES20.glGetUniformLocation(program, "uTexture"); + } + + /** + * Renders the mesh. This must be called on the GL thread. + * + * @param textureId GL_TEXTURE_EXTERNAL_OES used for this mesh. + * @param mvpMatrix The Model View Projection matrix. + * @param eyeType An {@link EyeType} value. + */ + /* package */ void draw(int textureId, float[] mvpMatrix, int eyeType) { + // Configure shader. + GLES20.glUseProgram(program); + checkGlError(); + + GLES20.glEnableVertexAttribArray(positionHandle); + GLES20.glEnableVertexAttribArray(texCoordsHandle); + checkGlError(); + + float[] texMatrix; + if (stereoMode == C.STEREO_MODE_TOP_BOTTOM) { + texMatrix = eyeType == EyeType.RIGHT ? TEX_MATRIX_BOTTOM : TEX_MATRIX_TOP; + } else if (stereoMode == C.STEREO_MODE_LEFT_RIGHT) { + texMatrix = eyeType == EyeType.RIGHT ? TEX_MATRIX_RIGHT : TEX_MATRIX_LEFT; + } else { + texMatrix = TEX_MATRIX_WHOLE; + } + GLES20.glUniformMatrix3fv(uTexMatrixHandle, 1, false, texMatrix, 0); + + GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + GLES20.glUniform1i(textureHandle, 0); + checkGlError(); + + MeshData meshData = + Assertions.checkNotNull(eyeType == EyeType.RIGHT ? rightMeshData : leftMeshData); + + // Load position data. + GLES20.glVertexAttribPointer( + positionHandle, + Projection.POSITION_COORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + Projection.POSITION_COORDS_PER_VERTEX * C.BYTES_PER_FLOAT, + meshData.vertexBuffer); + checkGlError(); + + // Load texture data. + GLES20.glVertexAttribPointer( + texCoordsHandle, + Projection.TEXTURE_COORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + Projection.TEXTURE_COORDS_PER_VERTEX * C.BYTES_PER_FLOAT, + meshData.textureBuffer); + checkGlError(); + + // Render. + GLES20.glDrawArrays(meshData.drawMode, 0, meshData.vertexCount); + checkGlError(); + + GLES20.glDisableVertexAttribArray(positionHandle); + GLES20.glDisableVertexAttribArray(texCoordsHandle); + } + + /** Cleans up the GL resources. */ + /* package */ void shutdown() { + if (program != 0) { + GLES20.glDeleteProgram(program); + } + } + + private static class MeshData { + private final int vertexCount; + private final FloatBuffer vertexBuffer; + private final FloatBuffer textureBuffer; + @Projection.DrawMode private final int drawMode; + + public MeshData(Projection.SubMesh subMesh) { + vertexCount = subMesh.getVertexCount(); + vertexBuffer = GlUtil.createBuffer(subMesh.vertices); + textureBuffer = GlUtil.createBuffer(subMesh.textureCoords); + switch (subMesh.mode) { + case Projection.DRAW_MODE_TRIANGLES_STRIP: + drawMode = GLES20.GL_TRIANGLE_STRIP; + break; + case Projection.DRAW_MODE_TRIANGLES_FAN: + drawMode = GLES20.GL_TRIANGLE_FAN; + break; + case Projection.DRAW_MODE_TRIANGLES: + default: + drawMode = GLES20.GL_TRIANGLES; + break; + } + } + } +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index 96788000ca..d529de1ccb 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -20,8 +20,9 @@ import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.support.annotation.Nullable; -import com.google.android.exoplayer2.ui.spherical.Mesh.EyeType; +import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.video.spherical.Projection; import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -33,14 +34,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /*package*/ final class SceneRenderer { private final AtomicBoolean frameAvailable; + private final ProjectionRenderer projectionRenderer; private int textureId; - @Nullable private SurfaceTexture surfaceTexture; - @MonotonicNonNull private Mesh mesh; - private boolean meshInitialized; + private @MonotonicNonNull SurfaceTexture surfaceTexture; + private @Nullable Projection pendingProjection; + private long pendingProjectionTimeNs; + private long lastFrameTimestamp; - public SceneRenderer() { + public SceneRenderer(Projection projection) { frameAvailable = new AtomicBoolean(); + projectionRenderer = new ProjectionRenderer(); + projectionRenderer.setProjection(projection); } /** Initializes the renderer. */ @@ -49,19 +54,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); checkGlError(); + projectionRenderer.init(); + checkGlError(); + textureId = GlUtil.createExternalTexture(); surfaceTexture = new SurfaceTexture(textureId); surfaceTexture.setOnFrameAvailableListener(surfaceTexture -> frameAvailable.set(true)); return surfaceTexture; } - /** Sets a {@link Mesh} to be used to display video. */ - public void setMesh(Mesh mesh) { - if (this.mesh != null) { - this.mesh.shutdown(); - } - this.mesh = mesh; - meshInitialized = false; + /** Sets a {@link Projection} to be used to display video. */ + public void setProjection(Projection projection, long timeNs) { + pendingProjection = projection; + pendingProjectionTimeNs = timeNs; } /** @@ -71,14 +76,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param eyeType an {@link EyeType} value */ public void drawFrame(float[] viewProjectionMatrix, int eyeType) { - if (mesh == null) { - return; - } - if (!meshInitialized) { - meshInitialized = true; - mesh.init(); - } - // glClear isn't strictly necessary when rendering fully spherical panoramas, but it can improve // performance on tiled renderers by causing the GPU to discard previous data. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); @@ -87,8 +84,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (frameAvailable.compareAndSet(true, false)) { Assertions.checkNotNull(surfaceTexture).updateTexImage(); checkGlError(); + lastFrameTimestamp = surfaceTexture.getTimestamp(); + } + if (pendingProjection != null && pendingProjectionTimeNs <= lastFrameTimestamp) { + projectionRenderer.setProjection(pendingProjection); + pendingProjection = null; } - mesh.draw(textureId, viewProjectionMatrix, eyeType); + projectionRenderer.draw(textureId, viewProjectionMatrix, eyeType); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index f4386a44c9..d90b1d31b3 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -37,9 +37,14 @@ import android.view.Display; import android.view.Surface; import android.view.WindowManager; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ui.spherical.Mesh.EyeType; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import com.google.android.exoplayer2.video.spherical.Projection; +import com.google.android.exoplayer2.video.spherical.ProjectionDecoder; +import java.util.Arrays; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -54,7 +59,8 @@ import javax.microedition.khronos.opengles.GL10; * match what they expect. */ @TargetApi(15) -public final class SphericalSurfaceView extends GLSurfaceView { +public final class SphericalSurfaceView extends GLSurfaceView + implements VideoFrameMetadataListener { /** * This listener can be used to be notified when the {@link Surface} associated with this view is @@ -70,17 +76,6 @@ public final class SphericalSurfaceView extends GLSurfaceView { void surfaceChanged(@Nullable Surface surface); } - // A spherical mesh for video should be large enough that there are no stereo artifacts. - private static final int SPHERE_RADIUS_METERS = 50; - - // TODO These should be configured based on the video type. It's assumed 360 video here. - private static final int DEFAULT_SPHERE_HORIZONTAL_DEGREES = 360; - private static final int DEFAULT_SPHERE_VERTICAL_DEGREES = 180; - - // The 360 x 180 sphere has 5 degree quads. Increase these if lines in videos look wavy. - private static final int DEFAULT_SPHERE_COLUMNS = 72; - private static final int DEFAULT_SPHERE_ROWS = 36; - // Arbitrary vertical field of view. private static final int FIELD_OF_VIEW_DEGREES = 90; private static final float Z_NEAR = .1f; @@ -99,6 +94,9 @@ public final class SphericalSurfaceView extends GLSurfaceView { private @Nullable SurfaceListener surfaceListener; private @Nullable SurfaceTexture surfaceTexture; private @Nullable Surface surface; + private @C.StreamType int defaultStereoMode; + private @C.StreamType int currentStereoMode; + private @Nullable byte[] currentProjectionData; public SphericalSurfaceView(Context context) { this(context, null); @@ -107,6 +105,8 @@ public final class SphericalSurfaceView extends GLSurfaceView { public SphericalSurfaceView(Context context, @Nullable AttributeSet attributeSet) { super(context, attributeSet); + defaultStereoMode = C.STEREO_MODE_MONO; + currentStereoMode = C.STEREO_MODE_MONO; mainHandler = new Handler(Looper.getMainLooper()); // Configure sensors and touch. @@ -129,30 +129,16 @@ public final class SphericalSurfaceView extends GLSurfaceView { setEGLContextClientVersion(2); setRenderer(renderer); setOnTouchListener(touchTracker); - - setStereoMode(C.STEREO_MODE_MONO); } /** - * Sets stereo mode of the media to be played. + * Sets the default stereo mode. If the played video doesn't contain a stereo mode the default one + * is used. * - * @param stereoMode One of {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link - * C#STEREO_MODE_LEFT_RIGHT}. + * @param stereoMode A {@link C.StereoMode} value. */ - public void setStereoMode(@C.StereoMode int stereoMode) { - Assertions.checkState( - stereoMode == C.STEREO_MODE_MONO - || stereoMode == C.STEREO_MODE_TOP_BOTTOM - || stereoMode == C.STEREO_MODE_LEFT_RIGHT); - Mesh mesh = - Mesh.createUvSphere( - SPHERE_RADIUS_METERS, - DEFAULT_SPHERE_ROWS, - DEFAULT_SPHERE_COLUMNS, - DEFAULT_SPHERE_VERTICAL_DEGREES, - DEFAULT_SPHERE_HORIZONTAL_DEGREES, - stereoMode); - queueEvent(() -> renderer.scene.setMesh(mesh)); + public void setDefaultStereoMode(@C.StereoMode int stereoMode) { + defaultStereoMode = stereoMode; } /** Returns the {@link Surface} associated with this view. */ @@ -169,6 +155,12 @@ public final class SphericalSurfaceView extends GLSurfaceView { surfaceListener = listener; } + @Override + public void onVideoFrameAboutToBeRendered( + long presentationTimeUs, long releaseTimeNs, Format format) { + setProjection(format.projectionData, format.stereoMode, releaseTimeNs); + } + @Override public void onResume() { super.onResume(); @@ -230,6 +222,35 @@ public final class SphericalSurfaceView extends GLSurfaceView { } } + /** + * Sets projection data and stereo mode of the media to be played. + * + * @param projectionData Contains the projection data to be rendered. + * @param stereoMode A {@link C.StereoMode} value. + * @param timeNs When then new projection should be used. + */ + private void setProjection( + @Nullable byte[] projectionData, @C.StereoMode int stereoMode, long timeNs) { + byte[] oldProjectionData = currentProjectionData; + int oldStereoMode = currentStereoMode; + currentProjectionData = projectionData; + currentStereoMode = stereoMode == Format.NO_VALUE ? defaultStereoMode : stereoMode; + if (oldStereoMode == currentStereoMode + && Arrays.equals(oldProjectionData, currentProjectionData)) { + return; + } + + Projection projectionFromData = null; + if (currentProjectionData != null) { + projectionFromData = ProjectionDecoder.decode(currentProjectionData, currentStereoMode); + } + Projection projection = + projectionFromData != null && ProjectionRenderer.isSupported(projectionFromData) + ? projectionFromData + : Projection.createEquirectangular(currentStereoMode); + queueEvent(() -> renderer.scene.setProjection(projection, timeNs)); + } + /** Detects sensor events and saves them as a matrix. */ private static class PhoneOrientationListener implements SensorEventListener { private final float[] phoneInWorldSpaceMatrix = new float[16]; @@ -328,7 +349,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { private final float[] tempMatrix = new float[16]; public Renderer() { - scene = new SceneRenderer(); + scene = new SceneRenderer(Projection.createEquirectangular(C.STEREO_MODE_MONO)); Matrix.setIdentityM(deviceOrientationMatrix, 0); Matrix.setIdentityM(touchPitchMatrix, 0); Matrix.setIdentityM(touchYawMatrix, 0); From e2ebb78b6345ae7e2095284880373f56fbc40237 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Aug 2018 06:43:30 -0700 Subject: [PATCH 18/42] Don't apply dependency check in non-app modules ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209757391 --- extensions/cast/build.gradle | 2 -- extensions/ima/build.gradle | 2 -- 2 files changed, 4 deletions(-) diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index 35499f1c1d..bee73cac12 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -50,8 +50,6 @@ dependencies { api 'com.android.support:recyclerview-v7:' + supportLibraryVersion } -apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' - ext { javadocTitle = 'Cast extension' } diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index cf6938a2b1..7fc7935cac 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -48,8 +48,6 @@ dependencies { testImplementation project(modulePrefix + 'testutils-robolectric') } -apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' - ext { javadocTitle = 'IMA extension' } From 9ccbb5bd6d7825021668a41fe1845ebcc0170cf6 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Aug 2018 06:51:42 -0700 Subject: [PATCH 19/42] Add some missing Nullable annotations Also remove NonNull, since we assume NonNull by default. Except where explicitly overriding a method with NonNull annotated args, in which case we're still expected to use it. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209758204 --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 2 +- .../exoplayer2/trackselection/DefaultTrackSelector.java | 3 ++- .../android/exoplayer2/upstream/DefaultHttpDataSource.java | 2 +- .../com/google/android/exoplayer2/upstream/cache/Cache.java | 3 --- .../google/android/exoplayer2/upstream/cache/SimpleCache.java | 3 ++- .../source/hls/playlist/DefaultHlsPlaylistTracker.java | 2 +- .../main/java/com/google/android/exoplayer2/ui/PlayerView.java | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) 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 0a80780148..1fbe058fb6 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 @@ -490,7 +490,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return codec; } - protected final MediaCodecInfo getCodecInfo() { + protected final @Nullable MediaCodecInfo getCodecInfo() { return codecInfo; } 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 58784e4c5a..87c4a50fd5 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 @@ -19,6 +19,7 @@ import android.content.Context; import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Pair; @@ -2032,7 +2033,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { * negative integer if this score is worse than the other. */ @Override - public int compareTo(AudioTrackScore other) { + public int compareTo(@NonNull AudioTrackScore other) { if (this.withinRendererCapabilitiesScore != other.withinRendererCapabilitiesScore) { return compareInts(this.withinRendererCapabilitiesScore, other.withinRendererCapabilitiesScore); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 87ea36bd18..06e3dc7e79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -379,7 +379,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou * * @return The current open connection, or null. */ - protected final HttpURLConnection getConnection() { + protected final @Nullable HttpURLConnection getConnection() { return connection; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java index 584939fdc7..a769e9acac 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.upstream.cache; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.io.File; import java.io.IOException; @@ -96,7 +95,6 @@ public interface Cache { * @param listener The listener to add. * @return The current spans for the key. */ - @NonNull NavigableSet addListener(String key, Listener listener); /** @@ -113,7 +111,6 @@ public interface Cache { * @param key The key for which spans should be returned. * @return The spans for the key. */ - @NonNull NavigableSet getCachedSpans(String key); /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index 7d2d5b79a9..14113479b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.upstream.cache; import android.os.ConditionVariable; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; @@ -224,7 +225,7 @@ public final class SimpleCache implements Cache { } @Override - public synchronized SimpleCacheSpan startReadWriteNonBlocking(String key, long position) + public synchronized @Nullable SimpleCacheSpan startReadWriteNonBlocking(String key, long position) throws CacheException { Assertions.checkState(!released); SimpleCacheSpan cacheSpan = getSpan(key, position); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index a61c8116ac..b2ebfa8375 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -155,7 +155,7 @@ public final class DefaultHlsPlaylistTracker } @Override - public HlsMasterPlaylist getMasterPlaylist() { + public @Nullable HlsMasterPlaylist getMasterPlaylist() { return masterPlaylist; } 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 e7c43f234f..472fa61613 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 @@ -597,7 +597,7 @@ public class PlayerView extends FrameLayout { } /** Returns the default artwork to display. */ - public Drawable getDefaultArtwork() { + public @Nullable Drawable getDefaultArtwork() { return defaultArtwork; } From 9f0303b0797bf9bbd403c25f85a3713dc38ef4b3 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Aug 2018 06:53:11 -0700 Subject: [PATCH 20/42] Fix a bunch of misc analysis warnings ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209758330 --- javadoc_combined.gradle | 4 ++-- .../java/com/google/android/exoplayer2/C.java | 1 + .../com/google/android/exoplayer2/Player.java | 21 +++++++++---------- .../android/exoplayer2/PlayerMessage.java | 2 +- .../google/android/exoplayer2/Renderer.java | 2 +- .../android/exoplayer2/drm/DrmSession.java | 6 ++---- .../exoplayer2/extractor/ts/AdtsReader.java | 3 +-- .../exoplayer2/text/tx3g/Tx3gDecoder.java | 4 ++-- .../upstream/cache/SimpleCache.java | 2 +- .../SilenceSkippingAudioProcessorTest.java | 4 ++-- .../source/MergingMediaSourceTest.java | 8 +++---- .../cache/CachedRegionTrackerTest.java | 3 +-- .../source/hls/offline/HlsDownloaderTest.java | 3 +-- .../manifest/SsManifestParser.java | 2 +- .../manifest/SsManifestTest.java | 4 +--- .../res/layout/exo_playback_control_view.xml | 4 +++- .../testutil/TestDownloadManagerListener.java | 2 +- 17 files changed, 35 insertions(+), 40 deletions(-) diff --git a/javadoc_combined.gradle b/javadoc_combined.gradle index aea65d4d97..209ad3a1a3 100644 --- a/javadoc_combined.gradle +++ b/javadoc_combined.gradle @@ -39,7 +39,7 @@ class CombinedJavadocPlugin implements Plugin { libraryModules.each { libraryModule -> libraryModule.android.libraryVariants.all { variant -> def name = variant.buildType.name - if (name.equals("release")) { + if (name == "release") { classpath += libraryModule.project.files( variant.javaCompile.classpath.files, @@ -63,7 +63,7 @@ class CombinedJavadocPlugin implements Plugin { } // Returns Android library modules that declare a generateJavadoc task. - private Set getLibraryModules(Project project) { + private static Set getLibraryModules(Project project) { project.subprojects.findAll { it.plugins.findPlugin("com.android.library") && it.tasks.findByName("generateJavadoc") diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6930975fca..096145b115 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -418,6 +418,7 @@ public final class C { /** Indicates that a buffer is (at least partially) encrypted. */ public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000 /** Indicates that a buffer should be decoded but not rendered. */ + @SuppressWarnings("NumericOverflow") public static final int BUFFER_FLAG_DECODE_ONLY = 1 << 31; // 0x80000000 /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 4711933f17..fddd4f1dea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -424,12 +424,10 @@ public interface Player { */ int STATE_ENDED = 4; - /** - * Repeat modes for playback. - */ + /** Repeat modes for playback. */ @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) - public @interface RepeatMode {} + @interface RepeatMode {} /** * Normal playback without repetition. */ @@ -452,7 +450,7 @@ public interface Player { DISCONTINUITY_REASON_AD_INSERTION, DISCONTINUITY_REASON_INTERNAL }) - public @interface DiscontinuityReason {} + @interface DiscontinuityReason {} /** * Automatic playback transition from one period in the timeline to the next. The period index may * be the same as it was before the discontinuity in case the current period is repeated. @@ -470,13 +468,14 @@ public interface Player { /** Discontinuity introduced internally by the source. */ int DISCONTINUITY_REASON_INTERNAL = 4; - /** - * Reasons for timeline and/or manifest changes. - */ + /** Reasons for timeline and/or manifest changes. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({TIMELINE_CHANGE_REASON_PREPARED, TIMELINE_CHANGE_REASON_RESET, - TIMELINE_CHANGE_REASON_DYNAMIC}) - public @interface TimelineChangeReason {} + @IntDef({ + TIMELINE_CHANGE_REASON_PREPARED, + TIMELINE_CHANGE_REASON_RESET, + TIMELINE_CHANGE_REASON_DYNAMIC + }) + @interface TimelineChangeReason {} /** * Timeline and manifest changed as a result of a player initialization with new media. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index 2c7aee834e..fb7145aad8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -231,7 +231,7 @@ public final class PlayerMessage { * Player.EventListener#onPlayerError(ExoPlaybackException)}. * * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. + * @throws IllegalStateException If this message has already been sent. */ public PlayerMessage send() { Assertions.checkState(!isSent); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index c29017856f..c1c640ca00 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -202,7 +202,7 @@ public interface Renderer extends PlayerMessage.Target { * @param operatingRate The operating rate. * @throws ExoPlaybackException If an error occurs handling the operating rate. */ - default void setOperatingRate(float operatingRate) throws ExoPlaybackException {}; + default void setOperatingRate(float operatingRate) throws ExoPlaybackException {} /** * Incrementally renders the {@link SampleStream}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index a3ae1d8b71..3d3b6b5b3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -39,12 +39,10 @@ public interface DrmSession { } - /** - * The state of the DRM session. - */ + /** The state of the DRM session. */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) - public @interface State {} + @interface State {} /** * The session has been released. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index 7f6a22b58b..0c59606ded 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -81,7 +81,6 @@ public final class AdtsReader implements ElementaryStreamReader { private int firstFrameSampleRateIndex; private int currentFrameVersion; - private int currentFrameSampleRateIndex; // Used when parsing the header. private boolean hasOutputFormat; @@ -327,7 +326,7 @@ public final class AdtsReader implements ElementaryStreamReader { adtsScratch.data[0] = buffer.data[buffer.getPosition()]; adtsScratch.setPosition(2); - currentFrameSampleRateIndex = adtsScratch.readBits(4); + int currentFrameSampleRateIndex = adtsScratch.readBits(4); if (firstFrameSampleRateIndex != C.INDEX_UNSET && currentFrameSampleRateIndex != firstFrameSampleRateIndex) { // Invalid header. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index ebc38bcd70..9211dc51ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -56,8 +56,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { private static final int FONT_FACE_ITALIC = 0x0002; private static final int FONT_FACE_UNDERLINE = 0x0004; - private static final int SPAN_PRIORITY_LOW = (0xFF << Spanned.SPAN_PRIORITY_SHIFT); - private static final int SPAN_PRIORITY_HIGH = (0x00 << Spanned.SPAN_PRIORITY_SHIFT); + private static final int SPAN_PRIORITY_LOW = 0xFF << Spanned.SPAN_PRIORITY_SHIFT; + private static final int SPAN_PRIORITY_HIGH = 0; private static final int DEFAULT_FONT_FACE = 0; private static final int DEFAULT_COLOR = Color.WHITE; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index 14113479b9..55f61705aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -191,7 +191,7 @@ public final class SimpleCache implements Cache { Assertions.checkState(!released); CachedContent cachedContent = index.get(key); return cachedContent == null || cachedContent.isEmpty() - ? new TreeSet() + ? new TreeSet<>() : new TreeSet(cachedContent.getSpans()); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java index 04de9a76f4..bd559218c6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java @@ -401,8 +401,8 @@ public final class SilenceSkippingAudioProcessorTest { public void appendFrames(int count, short... channelLevels) { Assertions.checkState(!built); for (int i = 0; i < count; i += channelCount) { - for (int j = 0; j < channelLevels.length; j++) { - buffer.put(channelLevels[j]); + for (short channelLevel : channelLevels) { + buffer.put(channelLevel); } } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java index e74347d2f4..3318f5a42f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java @@ -95,15 +95,15 @@ public class MergingMediaSourceTest { for (int i = 0; i < timelines.length; i++) { mediaSources[i] = new FakeMediaSource(timelines[i], null); } - MergingMediaSource mediaSource = new MergingMediaSource(mediaSources); - MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); + MergingMediaSource mergingMediaSource = new MergingMediaSource(mediaSources); + MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mergingMediaSource, null); try { Timeline timeline = testRunner.prepareSource(); // The merged timeline should always be the one from the first child. assertThat(timeline).isEqualTo(timelines[0]); testRunner.releaseSource(); - for (int i = 0; i < mediaSources.length; i++) { - mediaSources[i].assertReleased(); + for (FakeMediaSource mediaSource : mediaSources) { + mediaSource.assertReleased(); } } finally { testRunner.release(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java index 50f9cd2ae8..f8da2b1085 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java @@ -61,8 +61,7 @@ public final class CachedRegionTrackerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(cache.addListener(anyString(), any(Cache.Listener.class))) - .thenReturn(new TreeSet()); + when(cache.addListener(anyString(), any(Cache.Listener.class))).thenReturn(new TreeSet<>()); tracker = new CachedRegionTracker(cache, CACHE_KEY, CHUNK_INDEX); cacheDir = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); index = new CachedContentIndex(cacheDir); diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index acc5236311..b5ecad5b36 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -191,8 +191,7 @@ public class HlsDownloaderTest { private static ArrayList getKeys(int... variantIndices) { ArrayList streamKeys = new ArrayList<>(); for (int variantIndex : variantIndices) { - final int trackIndex = variantIndex; - streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, trackIndex)); + streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, variantIndex)); } return streamKeys; } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java index c2437db189..36eb6665f3 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -213,7 +213,7 @@ public class SsManifestParser implements ParsingLoadable.Parser { /** * @param xmlParser The underlying {@link XmlPullParser} - * @throws ParserException + * @throws ParserException If a parsing error occurs. */ protected void parseStartTag(XmlPullParser xmlParser) throws ParserException { // Do nothing. diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java index 05f2582f0d..dc8d6754f5 100644 --- a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java @@ -64,9 +64,7 @@ public class SsManifestTest { SsManifest sourceManifest = newSsManifest(newStreamElement("1", formats[0]), newStreamElement("2", formats[1])); - List keys = Arrays.asList(new StreamKey(1, 0)); - // Keys don't need to be in any particular order - Collections.shuffle(keys, new Random(0)); + List keys = Collections.singletonList(new StreamKey(1, 0)); SsManifest copyManifest = sourceManifest.copy(keys); diff --git a/library/ui/src/main/res/layout/exo_playback_control_view.xml b/library/ui/src/main/res/layout/exo_playback_control_view.xml index 159844c234..534655f2f4 100644 --- a/library/ui/src/main/res/layout/exo_playback_control_view.xml +++ b/library/ui/src/main/res/layout/exo_playback_control_view.xml @@ -14,12 +14,14 @@ limitations under the License. --> + android:orientation="vertical" + tools:targetApi="28"> getStateQueue(DownloadAction action) { synchronized (actionStates) { if (!actionStates.containsKey(action)) { - actionStates.put(action, new ArrayBlockingQueue(10)); + actionStates.put(action, new ArrayBlockingQueue<>(10)); } return actionStates.get(action); } From 509be44fe86f6f686d6aeb6645b1bae303ceabef Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 22 Aug 2018 07:07:40 -0700 Subject: [PATCH 21/42] Ignore cache span rename error This happens rarely and SimpleCache can continue fine. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209759996 --- .../exoplayer2/upstream/cache/CachedContent.java | 7 ++----- .../exoplayer2/upstream/cache/SimpleCache.java | 14 ++++++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java index 89835f31de..97a7828a22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java @@ -185,16 +185,13 @@ import java.util.TreeSet; * @throws CacheException If renaming of the underlying span file failed. */ public SimpleCacheSpan touch(SimpleCacheSpan cacheSpan) throws CacheException { - // Remove the old span from the in-memory representation. - Assertions.checkState(cachedSpans.remove(cacheSpan)); - // Obtain a new span with updated last access timestamp. SimpleCacheSpan newCacheSpan = cacheSpan.copyWithUpdatedLastAccessTime(id); - // Rename the cache file if (!cacheSpan.file.renameTo(newCacheSpan.file)) { throw new CacheException("Renaming of " + cacheSpan.file + " to " + newCacheSpan.file + " failed."); } - // Add the updated span back into the in-memory representation. + // Replace the in-memory representation of the span. + Assertions.checkState(cachedSpans.remove(cacheSpan)); cachedSpans.add(newCacheSpan); return newCacheSpan; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index 55f61705aa..adaf523e7c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -232,10 +232,16 @@ public final class SimpleCache implements Cache { // Read case. if (cacheSpan.isCached) { - // Obtain a new span with updated last access timestamp. - SimpleCacheSpan newCacheSpan = index.get(key).touch(cacheSpan); - notifySpanTouched(cacheSpan, newCacheSpan); - return newCacheSpan; + try { + // Obtain a new span with updated last access timestamp. + SimpleCacheSpan newCacheSpan = index.get(key).touch(cacheSpan); + notifySpanTouched(cacheSpan, newCacheSpan); + return newCacheSpan; + } catch (CacheException e) { + // Ignore. In worst case the cache span is evicted early. + // This happens very rarely [Internal: b/38351639] + return cacheSpan; + } } CachedContent cachedContent = index.getOrAdd(key); From 14c7c2995c612e011822cdf0cca0f85b637d6568 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 22 Aug 2018 07:29:16 -0700 Subject: [PATCH 22/42] Add android.permission.FOREGROUND_SERVICE Apps targeting P or later now must request this permission in order to use foreground services. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209762160 --- demos/main/src/main/AndroidManifest.xml | 1 + .../exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java | 1 + .../google/android/exoplayer2/scheduler/PlatformScheduler.java | 1 + 3 files changed, 3 insertions(+) diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 2234048ac1..e80e37688d 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + diff --git a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java index f75607f268..5227411266 100644 --- a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java +++ b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.Util; * *

{@literal
  * 
+ * 
  *
  * {@literal
  * 
+ * 
  *
  * 
Date: Wed, 22 Aug 2018 08:51:52 -0700
Subject: [PATCH 23/42] Improve IntDef javadoc.

The doc can be improved by enumerating and linking all possible values from
the interface doc.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=209772309
---
 .../ext/cronet/CronetEngineWrapper.java       |   3 +-
 .../exoplayer2/ext/flac/FlacExtractor.java    |   5 +-
 .../java/com/google/android/exoplayer2/C.java | 138 +++++++++++++-----
 .../exoplayer2/DefaultRenderersFactory.java   |   6 +-
 .../exoplayer2/ExoPlaybackException.java      |   3 +-
 .../com/google/android/exoplayer2/Player.java |  22 ++-
 .../google/android/exoplayer2/Renderer.java   |   7 +-
 .../android/exoplayer2/audio/Ac3Util.java     |   5 +-
 .../exoplayer2/audio/AudioFocusManager.java   |  14 +-
 .../decoder/DecoderInputBuffer.java           |  11 +-
 .../drm/DefaultDrmSessionManager.java         |   5 +-
 .../android/exoplayer2/drm/DrmSession.java    |   7 +-
 .../drm/UnsupportedDrmException.java          |   3 +-
 .../exoplayer2/extractor/Extractor.java       |   7 +-
 .../extractor/amr/AmrExtractor.java           |   5 +-
 .../extractor/mkv/EbmlReaderOutput.java       |   5 +-
 .../extractor/mkv/MatroskaExtractor.java      |   7 +-
 .../extractor/mp3/Mp3Extractor.java           |   7 +-
 .../extractor/mp4/FragmentedMp4Extractor.java |  17 ++-
 .../extractor/mp4/Mp4Extractor.java           |   7 +-
 .../exoplayer2/extractor/mp4/Track.java       |   3 +-
 .../extractor/ts/AdtsExtractor.java           |   5 +-
 .../ts/DefaultTsPayloadReaderFactory.java     |  19 ++-
 .../exoplayer2/extractor/ts/TsExtractor.java  |   3 +-
 .../exoplayer2/offline/DownloadManager.java   |   8 +-
 .../exoplayer2/scheduler/Requirements.java    |   5 +-
 .../source/ClippingMediaSource.java           |   5 +-
 .../exoplayer2/source/MergingMediaSource.java |   4 +-
 .../source/ads/AdPlaybackState.java           |   6 +-
 .../exoplayer2/source/ads/AdsMediaSource.java |   5 +-
 .../exoplayer2/text/CaptionStyleCompat.java   |  13 +-
 .../google/android/exoplayer2/text/Cue.java   |  12 +-
 .../text/webvtt/WebvttCssStyle.java           |  17 ++-
 .../trackselection/MappingTrackSelector.java  |   6 +-
 .../android/exoplayer2/upstream/DataSpec.java |  13 +-
 .../upstream/cache/CacheDataSource.java       |  17 ++-
 .../exoplayer2/util/EGLSurfaceTexture.java    |   5 +-
 .../exoplayer2/util/NotificationUtil.java     |   6 +-
 .../exoplayer2/util/RepeatModeUtil.java       |   9 +-
 .../source/hls/playlist/HlsMediaPlaylist.java |   4 +-
 .../exoplayer2/ui/AspectRatioFrameLayout.java |   6 +-
 .../ui/PlayerNotificationManager.java         |  20 ++-
 .../android/exoplayer2/ui/PlayerView.java     |   7 +-
 43 files changed, 356 insertions(+), 126 deletions(-)

diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
index db1394c1d6..1e24e3eb7c 100644
--- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
+++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
@@ -39,7 +39,8 @@ public final class CronetEngineWrapper {
   private final @CronetEngineSource int cronetEngineSource;
 
   /**
-   * Source of {@link CronetEngine}.
+   * Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link
+   * #SOURCE_UNKNOWN}, {@link #SOURCE_USER_PROVIDED} or {@link #SOURCE_UNAVAILABLE}.
    */
   @Retention(RetentionPolicy.SOURCE)
   @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE})
diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
index b6eec765d1..a1fbcc69d6 100644
--- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
+++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
@@ -50,7 +50,10 @@ public final class FlacExtractor implements Extractor {
   /** Factory that returns one extractor which is a {@link FlacExtractor}. */
   public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlacExtractor()};
 
-  /** Flags controlling the behavior of the extractor. */
+  /**
+   * Flags controlling the behavior of the extractor. Possible flag value is {@link
+   * #FLAG_DISABLE_ID3_METADATA}.
+   */
   @Retention(RetentionPolicy.SOURCE)
   @IntDef(
       flag = true,
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java
index 096145b115..c4bbc1a53e 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/C.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java
@@ -110,7 +110,8 @@ public final class C {
   public static final String SANS_SERIF_NAME = "sans-serif";
 
   /**
-   * Crypto modes for a codec.
+   * Crypto modes for a codec. One of {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR}
+   * or {@link #CRYPTO_MODE_AES_CBC}.
    */
   @Retention(RetentionPolicy.SOURCE)
   @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
@@ -134,7 +135,14 @@ public final class C {
    */
   public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
 
-  /** Represents an audio encoding, or an invalid or unset value. */
+  /**
+   * Represents an audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
+   * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
+   * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
+   * #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
+   * #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
+   * #ENCODING_DOLBY_TRUEHD}.
+   */
   @Retention(RetentionPolicy.SOURCE)
   @IntDef({
     Format.NO_VALUE,
@@ -154,7 +162,12 @@ public final class C {
   })
   public @interface Encoding {}
 
-  /** Represents a PCM audio encoding, or an invalid or unset value. */
+  /**
+   * Represents a PCM audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
+   * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
+   * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
+   * #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}.
+   */
   @Retention(RetentionPolicy.SOURCE)
   @IntDef({
     Format.NO_VALUE,
@@ -196,11 +209,22 @@ public final class C {
   public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
 
   /**
-   * Stream types for an {@link android.media.AudioTrack}.
+   * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
+   * #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link
+   * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link
+   * #STREAM_TYPE_USE_DEFAULT}.
    */
   @Retention(RetentionPolicy.SOURCE)
-  @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_DTMF, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION,
-      STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, STREAM_TYPE_USE_DEFAULT})
+  @IntDef({
+    STREAM_TYPE_ALARM,
+    STREAM_TYPE_DTMF,
+    STREAM_TYPE_MUSIC,
+    STREAM_TYPE_NOTIFICATION,
+    STREAM_TYPE_RING,
+    STREAM_TYPE_SYSTEM,
+    STREAM_TYPE_VOICE_CALL,
+    STREAM_TYPE_USE_DEFAULT
+  })
   public @interface StreamType {}
   /**
    * @see AudioManager#STREAM_ALARM
@@ -240,11 +264,18 @@ public final class C {
   public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
 
   /**
-   * Content types for {@link com.google.android.exoplayer2.audio.AudioAttributes}.
+   * Content types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. One of {@link
+   * #CONTENT_TYPE_MOVIE}, {@link #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link
+   * #CONTENT_TYPE_SPEECH} or {@link #CONTENT_TYPE_UNKNOWN}.
    */
   @Retention(RetentionPolicy.SOURCE)
-  @IntDef({CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, CONTENT_TYPE_SONIFICATION, CONTENT_TYPE_SPEECH,
-      CONTENT_TYPE_UNKNOWN})
+  @IntDef({
+    CONTENT_TYPE_MOVIE,
+    CONTENT_TYPE_MUSIC,
+    CONTENT_TYPE_SONIFICATION,
+    CONTENT_TYPE_SPEECH,
+    CONTENT_TYPE_UNKNOWN
+  })
   public @interface AudioContentType {}
   /**
    * @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE
@@ -271,13 +302,16 @@ public final class C {
       android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
 
   /**
-   * Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}.
-   * 

- * Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting the - * flag when tunneling is enabled via a track selector. + * Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}. Possible flag value is + * {@link #FLAG_AUDIBILITY_ENFORCED}. + * + *

Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting + * the flag when tunneling is enabled via a track selector. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) + @IntDef( + flag = true, + value = {FLAG_AUDIBILITY_ENFORCED}) public @interface AudioFlags {} /** * @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED @@ -285,7 +319,17 @@ public final class C { public static final int FLAG_AUDIBILITY_ENFORCED = android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED; - /** Usage types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. */ + /** + * Usage types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. One of {@link + * #USAGE_ALARM}, {@link #USAGE_ASSISTANCE_ACCESSIBILITY}, {@link + * #USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, {@link #USAGE_ASSISTANCE_SONIFICATION}, {@link + * #USAGE_ASSISTANT}, {@link #USAGE_GAME}, {@link #USAGE_MEDIA}, {@link #USAGE_NOTIFICATION}, + * {@link #USAGE_NOTIFICATION_COMMUNICATION_DELAYED}, {@link + * #USAGE_NOTIFICATION_COMMUNICATION_INSTANT}, {@link #USAGE_NOTIFICATION_COMMUNICATION_REQUEST}, + * {@link #USAGE_NOTIFICATION_EVENT}, {@link #USAGE_NOTIFICATION_RINGTONE}, {@link + * #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link + * #USAGE_VOICE_COMMUNICATION_SIGNALLING}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ USAGE_ALARM, @@ -377,7 +421,11 @@ public final class C { public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING; - /** Audio focus types. */ + /** + * Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link + * #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link + * #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ AUDIOFOCUS_NONE, @@ -401,11 +449,19 @@ public final class C { AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; /** - * Flags which can apply to a buffer containing a media sample. + * Flags which can apply to a buffer containing a media sample. Possible flag values are {@link + * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and + * {@link #BUFFER_FLAG_DECODE_ONLY}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, - BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY}) + @IntDef( + flag = true, + value = { + BUFFER_FLAG_KEY_FRAME, + BUFFER_FLAG_END_OF_STREAM, + BUFFER_FLAG_ENCRYPTED, + BUFFER_FLAG_DECODE_ONLY + }) public @interface BufferFlags {} /** * Indicates that a buffer holds a synchronization sample. @@ -422,7 +478,8 @@ public final class C { public static final int BUFFER_FLAG_DECODE_ONLY = 1 << 31; // 0x80000000 /** - * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. + * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. One of {@link + * #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}. */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) @@ -443,11 +500,13 @@ public final class C { public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT; /** - * Track selection flags. + * Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link + * #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, - SELECTION_FLAG_AUTOSELECT}) + @IntDef( + flag = true, + value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, SELECTION_FLAG_AUTOSELECT}) public @interface SelectionFlags {} /** * Indicates that the track should be selected if user preferences do not state otherwise. @@ -467,7 +526,8 @@ public final class C { public static final String LANGUAGE_UNDETERMINED = "und"; /** - * Represents a streaming or other media type. + * Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link + * #TYPE_HLS} or {@link #TYPE_OTHER}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) @@ -749,15 +809,17 @@ public final class C { public static final int MSG_CUSTOM_BASE = 10000; /** - * The stereo mode for 360/3D/VR videos. + * The stereo mode for 360/3D/VR videos. One of {@link Format#NO_VALUE}, {@link + * #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link + * #STEREO_MODE_STEREO_MESH}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - Format.NO_VALUE, - STEREO_MODE_MONO, - STEREO_MODE_TOP_BOTTOM, - STEREO_MODE_LEFT_RIGHT, - STEREO_MODE_STEREO_MESH + Format.NO_VALUE, + STEREO_MODE_MONO, + STEREO_MODE_TOP_BOTTOM, + STEREO_MODE_LEFT_RIGHT, + STEREO_MODE_STEREO_MESH }) public @interface StereoMode {} /** @@ -779,7 +841,8 @@ public final class C { public static final int STEREO_MODE_STEREO_MESH = 3; /** - * Video colorspaces. + * Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link + * #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) @@ -798,7 +861,8 @@ public final class C { public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; /** - * Video color transfer characteristics. + * Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link + * #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) @@ -817,7 +881,8 @@ public final class C { public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; /** - * Video color range. + * Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link + * #COLOR_RANGE_FULL}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) @@ -845,7 +910,12 @@ public final class C { */ public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000; - /** Network connection type. */ + /** + * Network connection type. One of {@link #NETWORK_TYPE_UNKNOWN}, {@link #NETWORK_TYPE_OFFLINE}, + * {@link #NETWORK_TYPE_WIFI}, {@link #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, {@link + * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or + * {@link #NETWORK_TYPE_OTHER}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_UNKNOWN, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 6cab53b78a..f625519b8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -52,11 +52,11 @@ public class DefaultRenderersFactory implements RenderersFactory { public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; /** - * Modes for using extension renderers. + * Modes for using extension renderers. One of {@link #EXTENSION_RENDERER_MODE_OFF}, {@link + * #EXTENSION_RENDERER_MODE_ON} or {@link #EXTENSION_RENDERER_MODE_PREFER}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, - EXTENSION_RENDERER_MODE_PREFER}) + @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER}) public @interface ExtensionRendererMode {} /** * Do not allow use of extension renderers. 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 ca7367f1b0..ba00d1163f 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 @@ -28,7 +28,8 @@ import java.lang.annotation.RetentionPolicy; public final class ExoPlaybackException extends Exception { /** - * The type of source that produced the error. + * The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} + * or {@link #TYPE_UNEXPECTED}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index fddd4f1dea..a00af72485 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -424,10 +424,13 @@ public interface Player { */ int STATE_ENDED = 4; - /** Repeat modes for playback. */ + /** + * Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link + * #REPEAT_MODE_ALL}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) - @interface RepeatMode {} + public @interface RepeatMode {} /** * Normal playback without repetition. */ @@ -441,7 +444,11 @@ public interface Player { */ int REPEAT_MODE_ALL = 2; - /** Reasons for position discontinuities. */ + /** + * Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_PERIOD_TRANSITION}, + * {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link + * #DISCONTINUITY_REASON_AD_INSERTION} or {@link #DISCONTINUITY_REASON_INTERNAL}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ DISCONTINUITY_REASON_PERIOD_TRANSITION, @@ -450,7 +457,7 @@ public interface Player { DISCONTINUITY_REASON_AD_INSERTION, DISCONTINUITY_REASON_INTERNAL }) - @interface DiscontinuityReason {} + public @interface DiscontinuityReason {} /** * Automatic playback transition from one period in the timeline to the next. The period index may * be the same as it was before the discontinuity in case the current period is repeated. @@ -468,14 +475,17 @@ public interface Player { /** Discontinuity introduced internally by the source. */ int DISCONTINUITY_REASON_INTERNAL = 4; - /** Reasons for timeline and/or manifest changes. */ + /** + * Reasons for timeline and/or manifest changes. One of {@link #TIMELINE_CHANGE_REASON_PREPARED}, + * {@link #TIMELINE_CHANGE_REASON_RESET} or {@link #TIMELINE_CHANGE_REASON_DYNAMIC}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ TIMELINE_CHANGE_REASON_PREPARED, TIMELINE_CHANGE_REASON_RESET, TIMELINE_CHANGE_REASON_DYNAMIC }) - @interface TimelineChangeReason {} + public @interface TimelineChangeReason {} /** * Timeline and manifest changed as a result of a player initialization with new media. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index c1c640ca00..d56b2f4f7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -34,10 +34,13 @@ import java.lang.annotation.RetentionPolicy; */ public interface Renderer extends PlayerMessage.Target { - /** The renderer states. */ + /** + * The renderer states. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} or {@link + * #STATE_STARTED}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) - @interface State {} + public @interface State {} /** * The renderer is disabled. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 94fe759a9b..b02fc1b1b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -33,7 +33,10 @@ public final class Ac3Util { /** Holds sample format information as presented by a syncframe header. */ public static final class SyncFrameInfo { - /** AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. */ + /** + * AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, + * {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2}) public @interface StreamType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index d078cddcc1..f1c24d0027 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -52,7 +52,10 @@ public final class AudioFocusManager { void executePlayerCommand(@PlayerCommand int playerCommand); } - /** Player commands. */ + /** + * Player commands. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link + * #PLAYER_COMMAND_WAIT_FOR_CALLBACK} or {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ PLAYER_COMMAND_DO_NOT_PLAY, @@ -134,8 +137,7 @@ public final class AudioFocusManager { * managed automatically. * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. * @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}. - * @return A command to execute on the player. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link - * #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. + * @return A {@link PlayerCommand} to execute on the player. */ public @PlayerCommand int setAudioAttributes( @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { @@ -169,8 +171,7 @@ public final class AudioFocusManager { * Called by a player as part of {@link ExoPlayer#prepare(MediaSource, boolean, boolean)}. * * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. - * @return A command to execute on the player. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link - * #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. + * @return A {@link PlayerCommand} to execute on the player. */ public @PlayerCommand int handlePrepare(boolean playWhenReady) { if (audioManager == null) { @@ -185,8 +186,7 @@ public final class AudioFocusManager { * * @param playWhenReady The desired value of playWhenReady. * @param playerState The current state of the player. - * @return A command to execute on the player. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link - * #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. + * @return A {@link PlayerCommand} to execute on the player. */ public @PlayerCommand int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { if (audioManager == null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index d22a45ce88..501d87d208 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -27,11 +27,16 @@ import java.nio.ByteBuffer; public class DecoderInputBuffer extends Buffer { /** - * The buffer replacement mode, which may disable replacement. + * The buffer replacement mode, which may disable replacement. One of {@link + * #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link + * #BUFFER_REPLACEMENT_MODE_DIRECT}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, - BUFFER_REPLACEMENT_MODE_DIRECT}) + @IntDef({ + BUFFER_REPLACEMENT_MODE_DISABLED, + BUFFER_REPLACEMENT_MODE_NORMAL, + BUFFER_REPLACEMENT_MODE_DIRECT + }) public @interface BufferReplacementMode {} /** * Disallows buffer replacement. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 895c27ad93..e7d23017d2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -67,7 +67,10 @@ public class DefaultDrmSessionManager implements DrmSe */ public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; - /** Determines the action to be done after a session acquired. */ + /** + * Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK}, + * {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) public @interface Mode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index 3d3b6b5b3c..40bbde4d8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -39,10 +39,13 @@ public interface DrmSession { } - /** The state of the DRM session. */ + /** + * The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link + * #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) - @interface State {} + public @interface State {} /** * The session has been released. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java index f0e748d722..5bea83d020 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java @@ -25,7 +25,8 @@ import java.lang.annotation.RetentionPolicy; public final class UnsupportedDrmException extends Exception { /** - * The reason for the exception. + * The reason for the exception. One of {@link #REASON_UNSUPPORTED_SCHEME} or {@link + * #REASON_INSTANTIATION_ERROR}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java index c63aad541b..47fe45bfc3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java @@ -44,10 +44,13 @@ public interface Extractor { */ int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; - /** Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. */ + /** + * Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. One of + * {@link #RESULT_CONTINUE}, {@link #RESULT_SEEK} or {@link #RESULT_END_OF_INPUT}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT}) - @interface ReadResult {} + public @interface ReadResult {} /** * Returns whether this extractor can extract samples from the {@link ExtractorInput}, which must diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java index b94ea7cb58..dfdce02450 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java @@ -47,7 +47,10 @@ public final class AmrExtractor implements Extractor { /** Factory for {@link AmrExtractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AmrExtractor()}; - /** Flags controlling the behavior of the extractor. */ + /** + * Flags controlling the behavior of the extractor. Possible flag value is {@link + * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java index b1cd508c8e..067c88b552 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -27,7 +27,10 @@ import java.lang.annotation.RetentionPolicy; */ /* package */ interface EbmlReaderOutput { - /** EBML element types. */ + /** + * EBML element types. One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link + * #TYPE_UNSIGNED_INT}, {@link #TYPE_STRING}, {@link #TYPE_BINARY} or {@link #TYPE_FLOAT}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT}) @interface ElementType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 355e299325..8e00628536 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -65,10 +65,13 @@ public final class MatroskaExtractor implements Extractor { public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new MatroskaExtractor()}; /** - * Flags controlling the behavior of the extractor. + * Flags controlling the behavior of the extractor. Possible flag value is {@link + * #FLAG_DISABLE_SEEK_FOR_CUES}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES}) + @IntDef( + flag = true, + value = {FLAG_DISABLE_SEEK_FOR_CUES}) public @interface Flags {} /** * Flag to disable seeking for cues. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 73dd0ec218..26a8bcce75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -47,10 +47,13 @@ public final class Mp3Extractor implements Extractor { public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp3Extractor()}; /** - * Flags controlling the behavior of the extractor. + * Flags controlling the behavior of the extractor. Possible flag values are {@link + * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA}) + @IntDef( + flag = true, + value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA}) public @interface Flags {} /** * Flag to force enable seeking using a constant bitrate assumption in cases where seeking would diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 12da11fd6b..f253016cf7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -62,12 +62,21 @@ public final class FragmentedMp4Extractor implements Extractor { () -> new Extractor[] {new FragmentedMp4Extractor()}; /** - * Flags controlling the behavior of the extractor. + * Flags controlling the behavior of the extractor. Possible flag values are {@link + * #FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME}, {@link #FLAG_WORKAROUND_IGNORE_TFDT_BOX}, + * {@link #FLAG_ENABLE_EMSG_TRACK}, {@link #FLAG_SIDELOADED} and {@link + * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, - FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_SIDELOADED, - FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) + @IntDef( + flag = true, + value = { + FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, + FLAG_WORKAROUND_IGNORE_TFDT_BOX, + FLAG_ENABLE_EMSG_TRACK, + FLAG_SIDELOADED, + FLAG_WORKAROUND_IGNORE_EDIT_LISTS + }) public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 5bb5e214c9..2aa9b86444 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -50,10 +50,13 @@ public final class Mp4Extractor implements Extractor, SeekMap { public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp4Extractor()}; /** - * Flags controlling the behavior of the extractor. + * Flags controlling the behavior of the extractor. Possible flag value is {@link + * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) + @IntDef( + flag = true, + value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) public @interface Flags {} /** * Flag to ignore any edit lists in the stream. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 3adc5a8972..867e037f4b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -28,7 +28,8 @@ import java.lang.annotation.RetentionPolicy; public final class Track { /** - * The transformation to apply to samples in the track, if any. + * The transformation to apply to samples in the track, if any. One of {@link + * #TRANSFORMATION_NONE} or {@link #TRANSFORMATION_CEA608_CDAT}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index ef7b763306..0c2a0545dc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -43,7 +43,10 @@ public final class AdtsExtractor implements Extractor { /** Factory for {@link AdtsExtractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AdtsExtractor()}; - /** Flags controlling the behavior of the extractor. */ + /** + * Flags controlling the behavior of the extractor. Possible flag value is {@link + * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 085e3443c1..06a60776c2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -34,13 +34,24 @@ import java.util.List; public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { /** - * Flags controlling elementary stream readers' behavior. + * Flags controlling elementary stream readers' behavior. Possible flag values are {@link + * #FLAG_ALLOW_NON_IDR_KEYFRAMES}, {@link #FLAG_IGNORE_AAC_STREAM}, {@link + * #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link + * #FLAG_IGNORE_SPLICE_INFO_STREAM} and {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, - FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM, - FLAG_OVERRIDE_CAPTION_DESCRIPTORS}) + @IntDef( + flag = true, + value = { + FLAG_ALLOW_NON_IDR_KEYFRAMES, + FLAG_IGNORE_AAC_STREAM, + FLAG_IGNORE_H264_STREAM, + FLAG_DETECT_ACCESS_UNITS, + FLAG_IGNORE_SPLICE_INFO_STREAM, + FLAG_OVERRIDE_CAPTION_DESCRIPTORS + }) public @interface Flags {} + public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1; public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 6234590afa..cef0eb8363 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -54,7 +54,8 @@ public final class TsExtractor implements Extractor { public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new TsExtractor()}; /** - * Modes for the extractor. + * Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link + * #MODE_HLS}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 36ac04505d..5b5ba6a095 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -521,7 +521,8 @@ public final class DownloadManager { public static final class TaskState { /** - * Task states. + * Task states. One of {@link #STATE_QUEUED}, {@link #STATE_STARTED}, {@link #STATE_COMPLETED}, + * {@link #STATE_CANCELED} or {@link #STATE_FAILED}. * *

Transition diagram: * @@ -601,7 +602,10 @@ public final class DownloadManager { private static final class Task implements Runnable { /** - * Task states. + * Task states. One of {@link TaskState#STATE_QUEUED}, {@link TaskState#STATE_STARTED}, {@link + * TaskState#STATE_COMPLETED}, {@link TaskState#STATE_CANCELED}, {@link TaskState#STATE_FAILED}, + * {@link #STATE_QUEUED_CANCELING}, {@link #STATE_STARTED_CANCELING} or {@link + * #STATE_STARTED_STOPPING}. * *

Transition map (vertical states are source states): * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 30b07da3eb..106102b133 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -35,7 +35,10 @@ import java.lang.annotation.RetentionPolicy; */ public final class Requirements { - /** Network types. */ + /** + * Network types. One of {@link #NETWORK_TYPE_NONE}, {@link #NETWORK_TYPE_ANY}, {@link + * #NETWORK_TYPE_UNMETERED}, {@link #NETWORK_TYPE_NOT_ROAMING} or {@link #NETWORK_TYPE_METERED}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_NONE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index f494856509..88e98e811f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -37,7 +37,10 @@ public final class ClippingMediaSource extends CompositeMediaSource { /** Thrown when a {@link ClippingMediaSource} cannot clip its wrapped source. */ public static final class IllegalClippingException extends IOException { - /** The reason clipping failed. */ + /** + * The reason clipping failed. One of {@link #REASON_INVALID_PERIOD_COUNT}, {@link + * #REASON_NOT_SEEKABLE_TO_START} or {@link #REASON_START_EXCEEDS_END}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_INVALID_PERIOD_COUNT, REASON_NOT_SEEKABLE_TO_START, REASON_START_EXCEEDS_END}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index b1367cb19f..746af5719e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -40,9 +40,7 @@ public final class MergingMediaSource extends CompositeMediaSource { */ public static final class IllegalMergeException extends IOException { - /** - * The reason the merge failed. - */ + /** The reason the merge failed. One of {@link #REASON_PERIOD_COUNT_MISMATCH}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_PERIOD_COUNT_MISMATCH}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 53f0a418be..bbcba3aab5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -234,7 +234,11 @@ public final class AdPlaybackState { } } - /** Represents the state of an ad in an ad group. */ + /** + * Represents the state of an ad in an ad group. One of {@link #AD_STATE_UNAVAILABLE}, {@link + * #AD_STATE_AVAILABLE}, {@link #AD_STATE_SKIPPED}, {@link #AD_STATE_PLAYED} or {@link + * #AD_STATE_ERROR}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ AD_STATE_UNAVAILABLE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 0e218d5efe..7ff3b5a9b3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -82,7 +82,10 @@ public final class AdsMediaSource extends CompositeMediaSource { */ public static final class AdLoadException extends IOException { - /** Types of ad load exceptions. */ + /** + * Types of ad load exceptions. One of {@link #TYPE_AD}, {@link #TYPE_AD_GROUP}, {@link + * #TYPE_ALL_ADS} or {@link #TYPE_UNEXPECTED}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_AD, TYPE_AD_GROUP, TYPE_ALL_ADS, TYPE_UNEXPECTED}) public @interface Type {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java index 51f5ad0a64..87dcb97a81 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java @@ -31,11 +31,18 @@ import java.lang.annotation.RetentionPolicy; public final class CaptionStyleCompat { /** - * The type of edge, which may be none. + * The type of edge, which may be none. One of {@link #EDGE_TYPE_NONE}, {@link + * #EDGE_TYPE_OUTLINE}, {@link #EDGE_TYPE_DROP_SHADOW}, {@link #EDGE_TYPE_RAISED} or {@link + * #EDGE_TYPE_DEPRESSED}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED, - EDGE_TYPE_DEPRESSED}) + @IntDef({ + EDGE_TYPE_NONE, + EDGE_TYPE_OUTLINE, + EDGE_TYPE_DROP_SHADOW, + EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED + }) public @interface EdgeType {} /** * Edge type value specifying no character edges. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index 8bc0b8e136..e1305acd14 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -33,7 +33,8 @@ public class Cue { public static final float DIMEN_UNSET = Float.MIN_VALUE; /** - * The type of anchor, which may be unset. + * The type of anchor, which may be unset. One of {@link #TYPE_UNSET}, {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE} or {@link #ANCHOR_TYPE_END}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) @@ -62,7 +63,8 @@ public class Cue { public static final int ANCHOR_TYPE_END = 2; /** - * The type of line, which may be unset. + * The type of line, which may be unset. One of {@link #TYPE_UNSET}, {@link #LINE_TYPE_FRACTION} + * or {@link #LINE_TYPE_NUMBER}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) @@ -78,7 +80,11 @@ public class Cue { */ public static final int LINE_TYPE_NUMBER = 1; - /** The type of default text size for this cue, which may be unset. */ + /** + * The type of default text size for this cue, which may be unset. One of {@link #TYPE_UNSET}, + * {@link #TEXT_SIZE_TYPE_FRACTIONAL}, {@link #TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING} or {@link + * #TEXT_SIZE_TYPE_ABSOLUTE}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ TYPE_UNSET, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java index a78c5afa78..0e46fa0d2f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -35,20 +35,29 @@ public final class WebvttCssStyle { public static final int UNSPECIFIED = -1; - /** Style flag enum */ + /** + * Style flag enum. Possible flag values are {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link + * #STYLE_BOLD}, {@link #STYLE_ITALIC} and {@link #STYLE_BOLD_ITALIC}. + */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, - STYLE_BOLD_ITALIC}) + @IntDef( + flag = true, + value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, STYLE_BOLD_ITALIC}) public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; - /** Font size unit enum */ + /** + * Font size unit enum. One of {@link #UNSPECIFIED}, {@link #FONT_SIZE_UNIT_PIXEL}, {@link + * #FONT_SIZE_UNIT_EM} or {@link #FONT_SIZE_UNIT_PERCENT}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 99e4e58c4a..a243b1a813 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -43,7 +43,11 @@ public abstract class MappingTrackSelector extends TrackSelector { */ public static final class MappedTrackInfo { - /** Levels of renderer support. Higher numerical values indicate higher levels of support. */ + /** + * Levels of renderer support. Higher numerical values indicate higher levels of support. One of + * {@link #RENDERER_SUPPORT_NO_TRACKS}, {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS}, {@link + * #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS} or {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ RENDERER_SUPPORT_NO_TRACKS, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index 366b6d8c67..653673ed97 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -30,10 +30,13 @@ import java.util.Arrays; public final class DataSpec { /** - * The flags that apply to any request for data. + * The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP} + * and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}) + @IntDef( + flag = true, + value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}) public @interface Flags {} /** * Permits an underlying network stack to request that the server use gzip compression. @@ -54,7 +57,11 @@ public final class DataSpec { */ public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; // 2 - /** The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. */ + /** + * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link + * #HTTP_METHOD_GET}, {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}. + */ + @Retention(RetentionPolicy.SOURCE) @IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD}) public @interface HttpMethod {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 222d5385d3..a91e3246cc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -56,11 +56,17 @@ public final class CacheDataSource implements DataSource { public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024; /** - * Flags controlling the cache's behavior. + * Flags controlling the cache's behavior. Possible flag values are {@link #FLAG_BLOCK_ON_CACHE}, + * {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, - FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}) + @IntDef( + flag = true, + value = { + FLAG_BLOCK_ON_CACHE, + FLAG_IGNORE_CACHE_ON_ERROR, + FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS + }) public @interface Flags {} /** * A flag indicating whether we will block reads if the cache key is locked. If unset then data is @@ -81,7 +87,10 @@ public final class CacheDataSource implements DataSource { */ public static final int FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS = 1 << 2; // 4 - /** Reasons the cache may be ignored. */ + /** + * Reasons the cache may be ignored. One of {@link #CACHE_IGNORED_REASON_ERROR} or {@link + * #CACHE_IGNORED_REASON_UNSET_LENGTH}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_IGNORED_REASON_ERROR, CACHE_IGNORED_REASON_UNSET_LENGTH}) public @interface CacheIgnoredReason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index 7e831f0512..90e37de828 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -39,7 +39,10 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL void onFrameAvailable(); } - /** Secure mode to be used by the EGL surface and context. */ + /** + * Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link + * #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) public @interface SecureMode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java index c93d7cd72e..e70f576754 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java @@ -31,7 +31,11 @@ import java.lang.annotation.RetentionPolicy; @SuppressLint("InlinedApi") public final class NotificationUtil { - /** Notification channel importance levels. */ + /** + * Notification channel importance levels. One of {@link #IMPORTANCE_UNSPECIFIED}, {@link + * #IMPORTANCE_NONE}, {@link #IMPORTANCE_MIN}, {@link #IMPORTANCE_LOW}, {@link + * #IMPORTANCE_DEFAULT} or {@link #IMPORTANCE_HIGH}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ IMPORTANCE_UNSPECIFIED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java index d386206bdd..de92e1ad93 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -26,11 +26,14 @@ import java.lang.annotation.RetentionPolicy; public final class RepeatModeUtil { /** - * Set of repeat toggle modes. Can be combined using bit-wise operations. + * Set of repeat toggle modes. Can be combined using bit-wise operations. Possible flag values are + * {@link #REPEAT_TOGGLE_MODE_NONE}, {@link #REPEAT_TOGGLE_MODE_ONE} and {@link + * #REPEAT_TOGGLE_MODE_ALL}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE, - REPEAT_TOGGLE_MODE_ALL}) + @IntDef( + flag = true, + value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE, REPEAT_TOGGLE_MODE_ALL}) public @interface RepeatToggleModes {} /** * All repeat mode buttons disabled. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 841c13f953..a29808933b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -154,11 +154,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } /** - * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. + * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link + * #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) public @interface PlaylistType {} + public static final int PLAYLIST_TYPE_UNKNOWN = 0; public static final int PLAYLIST_TYPE_VOD = 1; public static final int PLAYLIST_TYPE_EVENT = 2; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java index 227eb52e79..0158a55f66 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -45,7 +45,11 @@ public final class AspectRatioFrameLayout extends FrameLayout { } // LINT.IfChange - /** Resize modes for {@link AspectRatioFrameLayout}. */ + /** + * Resize modes for {@link AspectRatioFrameLayout}. One of {@link #RESIZE_MODE_FIT}, {@link + * #RESIZE_MODE_FIXED_WIDTH}, {@link #RESIZE_MODE_FIXED_HEIGHT}, {@link #RESIZE_MODE_FILL} or + * {@link #RESIZE_MODE_ZOOM}. + */ @Retention(RetentionPolicy.SOURCE) @IntDef({ RESIZE_MODE_FIT, diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 09bcac8861..805ae0fa67 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,8 +15,6 @@ */ package com.google.android.exoplayer2.ui; -import static java.lang.annotation.RetentionPolicy.SOURCE; - import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -45,6 +43,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -229,8 +228,12 @@ public class PlayerNotificationManager { /** The action which cancels the notification and stops playback. */ public static final String ACTION_STOP = "com.google.android.exoplayer.stop"; - /** Visibility of notification on the lock screen. */ - @Retention(SOURCE) + /** + * Visibility of notification on the lock screen. One of {@link + * NotificationCompat#VISIBILITY_PRIVATE}, {@link NotificationCompat#VISIBILITY_PUBLIC} or {@link + * NotificationCompat#VISIBILITY_SECRET}. + */ + @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.VISIBILITY_PRIVATE, NotificationCompat.VISIBILITY_PUBLIC, @@ -238,8 +241,13 @@ public class PlayerNotificationManager { }) public @interface Visibility {} - /** Priority of the notification (required for API 25 and lower). */ - @Retention(SOURCE) + /** + * Priority of the notification (required for API 25 and lower). One of {@link + * NotificationCompat#PRIORITY_DEFAULT}, {@link NotificationCompat#PRIORITY_MAX}, {@link + * NotificationCompat#PRIORITY_HIGH}, {@link NotificationCompat#PRIORITY_LOW }or {@link + * NotificationCompat#PRIORITY_MIN}. + */ + @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.PRIORITY_DEFAULT, NotificationCompat.PRIORITY_MAX, 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 472fa61613..011976ad12 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 @@ -243,9 +243,12 @@ public class PlayerView extends FrameLayout { private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; private static final int SURFACE_TYPE_MONO360_VIEW = 3; - /** Determines when the buffering view is shown. */ - @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) + /** + * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link + * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. + */ @Retention(RetentionPolicy.SOURCE) + @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) public @interface ShowBuffering {} /** The buffering view is never shown. */ public static final int SHOW_BUFFERING_NEVER = 0; From 74e2384fb6e129e273f955616ad31aa127f505d4 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 23 Aug 2018 02:00:37 -0700 Subject: [PATCH 24/42] Add response headers to LoadEventInfo. The response headers of the last load are available from the loading source when creating media source events and can be easily forwarded. Issue:#4361 Issue:#4615 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209900693 --- RELEASENOTES.md | 4 ++ .../source/ExtractorMediaPeriod.java | 4 +- .../source/MediaSourceEventListener.java | 47 +++++++++++++++---- .../source/SingleSampleMediaPeriod.java | 4 +- .../exoplayer2/source/ads/AdsMediaSource.java | 3 ++ .../source/chunk/ChunkSampleStream.java | 4 +- .../exoplayer2/upstream/ParsingLoadable.java | 10 ++++ .../source/dash/DashMediaSource.java | 8 +++- .../source/hls/HlsSampleStreamWrapper.java | 4 +- .../playlist/DefaultHlsPlaylistTracker.java | 8 +++- .../source/smoothstreaming/SsMediaSource.java | 6 ++- .../exoplayer2/testutil/FakeMediaPeriod.java | 3 +- .../exoplayer2/testutil/FakeMediaSource.java | 3 ++ 13 files changed, 88 insertions(+), 20 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 86cef02a1b..23950c958f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -101,6 +101,10 @@ * Add uri field to `LoadEventInfo` in `MediaSourceEventListener` or `AnalyticsListener` callbacks. This uri is the redirected uri if redirection occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)). +* Add response headers field to `LoadEventInfo` in `MediaSourceEventListener` or + `AnalyticsListener` callbacks + ([#4361](https://github.com/google/ExoPlayer/issues/4361) and + [#4615](https://github.com/google/ExoPlayer/issues/4615)). * Allow `MediaCodecSelector`s to return multiple compatible decoders for `MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that falls back to less preferred decoders like `MediaCodec.createDecoderByType` diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index d26695afa7..7e44322f20 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -503,6 +503,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; eventDispatcher.loadCompleted( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, @@ -524,6 +525,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; eventDispatcher.loadCanceled( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, @@ -570,6 +572,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; eventDispatcher.loadError( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, @@ -697,7 +700,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType)); eventDispatcher.loadStarted( loadable.dataSpec, - loadable.dataSpec.uri, C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java index 844534a43d..98d1d0a2ab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java @@ -28,6 +28,9 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; /** Interface for callbacks to be notified of {@link MediaSource} events. */ @@ -44,6 +47,8 @@ public interface MediaSourceEventListener { * after redirection. */ public final Uri uri; + /** The response headers associated with the load, or an empty map if unavailable. */ + public final Map> responseHeaders; /** The value of {@link SystemClock#elapsedRealtime} at the time of the load event. */ public final long elapsedRealtimeMs; /** The duration of the load up to the event time. */ @@ -58,6 +63,8 @@ public interface MediaSourceEventListener { * @param uri The {@link Uri} from which data is being read. The uri must be identical to the * one in {@code dataSpec.uri} unless redirection has occurred. If redirection has occurred, * this is the uri after redirection. + * @param responseHeaders The response headers associated with the load, or an empty map if + * unavailable. * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} at the time of the * load event. * @param loadDurationMs The duration of the load up to the event time. @@ -65,9 +72,15 @@ public interface MediaSourceEventListener { * network responses, this is the decompressed size. */ public LoadEventInfo( - DataSpec dataSpec, Uri uri, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) { + DataSpec dataSpec, + Uri uri, + Map> responseHeaders, + long elapsedRealtimeMs, + long loadDurationMs, + long bytesLoaded) { this.dataSpec = dataSpec; this.uri = uri; + this.responseHeaders = responseHeaders; this.elapsedRealtimeMs = elapsedRealtimeMs; this.loadDurationMs = loadDurationMs; this.bytesLoaded = bytesLoaded; @@ -168,7 +181,8 @@ public interface MediaSourceEventListener { * @param mediaPeriodId The {@link MediaPeriodId} this load belongs to. Null if the load does not * belong to a specific media period. * @param loadEventInfo The {@link LoadEventInfo} corresponding to the event. The value of {@link - * LoadEventInfo#uri} won't reflect potential redirection yet. + * LoadEventInfo#uri} won't reflect potential redirection yet and {@link + * LoadEventInfo#responseHeaders} will be empty. * @param mediaLoadData The {@link MediaLoadData} defining the data being loaded. */ void onLoadStarted( @@ -370,10 +384,9 @@ public interface MediaSourceEventListener { } /** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */ - public void loadStarted(DataSpec dataSpec, Uri uri, int dataType, long elapsedRealtimeMs) { + public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) { loadStarted( dataSpec, - uri, dataType, C.TRACK_TYPE_UNKNOWN, null, @@ -387,7 +400,6 @@ public interface MediaSourceEventListener { /** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */ public void loadStarted( DataSpec dataSpec, - Uri uri, int dataType, int trackType, @Nullable Format trackFormat, @@ -398,7 +410,12 @@ public interface MediaSourceEventListener { long elapsedRealtimeMs) { loadStarted( new LoadEventInfo( - dataSpec, uri, elapsedRealtimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0), + dataSpec, + dataSpec.uri, + /* responseHeaders= */ Collections.emptyMap(), + elapsedRealtimeMs, + /* loadDurationMs= */ 0, + /* bytesLoaded= */ 0), new MediaLoadData( dataType, trackType, @@ -423,6 +440,7 @@ public interface MediaSourceEventListener { public void loadCompleted( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, long elapsedRealtimeMs, long loadDurationMs, @@ -430,6 +448,7 @@ public interface MediaSourceEventListener { loadCompleted( dataSpec, uri, + responseHeaders, dataType, C.TRACK_TYPE_UNKNOWN, null, @@ -446,6 +465,7 @@ public interface MediaSourceEventListener { public void loadCompleted( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, int trackType, @Nullable Format trackFormat, @@ -457,7 +477,8 @@ public interface MediaSourceEventListener { long loadDurationMs, long bytesLoaded) { loadCompleted( - new LoadEventInfo(dataSpec, uri, elapsedRealtimeMs, loadDurationMs, bytesLoaded), + new LoadEventInfo( + dataSpec, uri, responseHeaders, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new MediaLoadData( dataType, trackType, @@ -483,6 +504,7 @@ public interface MediaSourceEventListener { public void loadCanceled( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, long elapsedRealtimeMs, long loadDurationMs, @@ -490,6 +512,7 @@ public interface MediaSourceEventListener { loadCanceled( dataSpec, uri, + responseHeaders, dataType, C.TRACK_TYPE_UNKNOWN, null, @@ -506,6 +529,7 @@ public interface MediaSourceEventListener { public void loadCanceled( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, int trackType, @Nullable Format trackFormat, @@ -517,7 +541,8 @@ public interface MediaSourceEventListener { long loadDurationMs, long bytesLoaded) { loadCanceled( - new LoadEventInfo(dataSpec, uri, elapsedRealtimeMs, loadDurationMs, bytesLoaded), + new LoadEventInfo( + dataSpec, uri, responseHeaders, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new MediaLoadData( dataType, trackType, @@ -546,6 +571,7 @@ public interface MediaSourceEventListener { public void loadError( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, long elapsedRealtimeMs, long loadDurationMs, @@ -555,6 +581,7 @@ public interface MediaSourceEventListener { loadError( dataSpec, uri, + responseHeaders, dataType, C.TRACK_TYPE_UNKNOWN, null, @@ -576,6 +603,7 @@ public interface MediaSourceEventListener { public void loadError( DataSpec dataSpec, Uri uri, + Map> responseHeaders, int dataType, int trackType, @Nullable Format trackFormat, @@ -589,7 +617,8 @@ public interface MediaSourceEventListener { IOException error, boolean wasCanceled) { loadError( - new LoadEventInfo(dataSpec, uri, elapsedRealtimeMs, loadDurationMs, bytesLoaded), + new LoadEventInfo( + dataSpec, uri, responseHeaders, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new MediaLoadData( dataType, trackType, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index 458148499a..f53dd594e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -155,7 +155,6 @@ import java.util.Arrays; loadErrorHandlingPolicy.getMinimumLoadableRetryCount(C.DATA_TYPE_MEDIA)); eventDispatcher.loadStarted( dataSpec, - dataSpec.uri, C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, format, @@ -211,6 +210,7 @@ import java.util.Arrays; eventDispatcher.loadCompleted( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, format, @@ -229,6 +229,7 @@ import java.util.Arrays; eventDispatcher.loadCanceled( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, @@ -269,6 +270,7 @@ import java.util.Arrays; eventDispatcher.loadError( loadable.dataSpec, loadable.dataSource.getLastOpenedUri(), + loadable.dataSource.getLastResponseHeaders(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, format, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 7ff3b5a9b3..3d5c41e8bc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -43,6 +43,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -556,6 +557,7 @@ public final class AdsMediaSource extends CompositeMediaSource { .loadError( dataSpec, dataSpec.uri, + /* responseHeaders= */ Collections.emptyMap(), C.DATA_TYPE_AD, C.TRACK_TYPE_UNKNOWN, /* loadDurationMs= */ 0, @@ -595,6 +597,7 @@ public final class AdsMediaSource extends CompositeMediaSource { .loadError( new DataSpec(adUri), adUri, + /* responseHeaders= */ Collections.emptyMap(), C.DATA_TYPE_AD, C.TRACK_TYPE_UNKNOWN, /* loadDurationMs= */ 0, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 78914e9f33..383f3dfc15 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -432,6 +432,7 @@ public class ChunkSampleStream implements SampleStream, S eventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, primaryTrackType, loadable.trackFormat, @@ -451,6 +452,7 @@ public class ChunkSampleStream implements SampleStream, S eventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, primaryTrackType, loadable.trackFormat, @@ -518,6 +520,7 @@ public class ChunkSampleStream implements SampleStream, S eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, primaryTrackType, loadable.trackFormat, @@ -585,7 +588,6 @@ public class ChunkSampleStream implements SampleStream, S loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)); eventDispatcher.loadStarted( loadable.dataSpec, - loadable.dataSpec.uri, loadable.type, primaryTrackType, loadable.trackFormat, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java index 17d479daab..cdcb3787fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java @@ -24,6 +24,8 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.io.InputStream; +import java.util.List; +import java.util.Map; /** * A {@link Loadable} for objects that can be parsed from binary data using a {@link Parser}. @@ -132,6 +134,14 @@ public final class ParsingLoadable implements Loadable { return dataSource.getLastOpenedUri(); } + /** + * Returns the response headers associated with the load. Must only be called after the load + * completed, failed, or was canceled. + */ + public Map> getResponseHeaders() { + return dataSource.getLastResponseHeaders(); + } + @Override public final void cancelLoad() { // Do nothing. 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 e9d9c42e0b..6546863f66 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 @@ -709,6 +709,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestEventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -800,6 +801,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestEventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -814,6 +816,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestEventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -829,6 +832,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestEventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -844,6 +848,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestEventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -1031,8 +1036,7 @@ public final class DashMediaSource extends BaseMediaSource { private void startLoading(ParsingLoadable loadable, Loader.Callback> callback, int minRetryCount) { long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount); - manifestEventDispatcher.loadStarted( - loadable.dataSpec, loadable.dataSpec.uri, loadable.type, elapsedRealtimeMs); + manifestEventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); } private long getNowUnixTimeUs() { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 5c63e19f28..9bfdae1cf4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -571,7 +571,6 @@ import java.util.List; loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)); eventDispatcher.loadStarted( loadable.dataSpec, - loadable.dataSpec.uri, loadable.type, trackType, loadable.trackFormat, @@ -596,6 +595,7 @@ import java.util.List; eventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, trackType, loadable.trackFormat, @@ -619,6 +619,7 @@ import java.util.List; eventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, trackType, loadable.trackFormat, @@ -680,6 +681,7 @@ import java.util.List; eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, trackType, loadable.trackFormat, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index b2ebfa8375..ac94d7307e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -123,7 +123,6 @@ public final class DefaultHlsPlaylistTracker loadErrorHandlingPolicy.getMinimumLoadableRetryCount(masterPlaylistLoadable.type)); eventDispatcher.loadStarted( masterPlaylistLoadable.dataSpec, - masterPlaylistLoadable.dataSpec.uri, masterPlaylistLoadable.type, elapsedRealtime); } @@ -234,6 +233,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -249,6 +249,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -269,6 +270,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -496,6 +498,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -514,6 +517,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -555,6 +559,7 @@ public final class DefaultHlsPlaylistTracker eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, @@ -583,7 +588,6 @@ public final class DefaultHlsPlaylistTracker loadErrorHandlingPolicy.getMinimumLoadableRetryCount(mediaPlaylistLoadable.type)); eventDispatcher.loadStarted( mediaPlaylistLoadable.dataSpec, - mediaPlaylistLoadable.dataSpec.uri, mediaPlaylistLoadable.type, elapsedRealtime); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index efd733d651..83f1a59c8b 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -569,6 +569,7 @@ public final class SsMediaSource extends BaseMediaSource manifestEventDispatcher.loadCompleted( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -585,6 +586,7 @@ public final class SsMediaSource extends BaseMediaSource manifestEventDispatcher.loadCanceled( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -602,6 +604,7 @@ public final class SsMediaSource extends BaseMediaSource manifestEventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), + loadable.getResponseHeaders(), loadable.type, elapsedRealtimeMs, loadDurationMs, @@ -692,8 +695,7 @@ public final class SsMediaSource extends BaseMediaSource long elapsedRealtimeMs = manifestLoader.startLoading( loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)); - manifestEventDispatcher.loadStarted( - loadable.dataSpec, loadable.dataSpec.uri, loadable.type, elapsedRealtimeMs); + manifestEventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java index 4e3713a4c6..f2739f2b4d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; +import java.util.Collections; /** * Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting @@ -115,7 +116,6 @@ public class FakeMediaPeriod implements MediaPeriod { public synchronized void prepare(Callback callback, long positionUs) { eventDispatcher.loadStarted( FAKE_DATA_SPEC, - FAKE_DATA_SPEC.uri, C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, @@ -228,6 +228,7 @@ public class FakeMediaPeriod implements MediaPeriod { eventDispatcher.loadCompleted( FAKE_DATA_SPEC, FAKE_DATA_SPEC.uri, + /* responseHeaders= */ Collections.emptyMap(), C.DATA_TYPE_MEDIA, C.TRACK_TYPE_UNKNOWN, /* trackFormat= */ null, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 82d6026482..2fca4f42c7 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -225,6 +226,7 @@ public class FakeMediaSource extends BaseMediaSource { new LoadEventInfo( FAKE_DATA_SPEC, FAKE_DATA_SPEC.uri, + /* responseHeaders= */ Collections.emptyMap(), elapsedRealTimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0), @@ -233,6 +235,7 @@ public class FakeMediaSource extends BaseMediaSource { new LoadEventInfo( FAKE_DATA_SPEC, FAKE_DATA_SPEC.uri, + /* responseHeaders= */ Collections.emptyMap(), elapsedRealTimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ MANIFEST_LOAD_BYTES), From 3f70454cc2fdd80de30a40f55534322e94500092 Mon Sep 17 00:00:00 2001 From: eguven Date: Thu, 23 Aug 2018 03:19:30 -0700 Subject: [PATCH 25/42] Fix controller ui toggling when using SphericalSurfaceView ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209909845 --- .../android/exoplayer2/ui/PlayerView.java | 33 ++++++-- .../ui/spherical/SingleTapListener.java | 29 +++++++ .../ui/spherical/SphericalSurfaceView.java | 8 +- .../exoplayer2/ui/spherical/TouchTracker.java | 83 ++++++++++++------- 4 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SingleTapListener.java 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 011976ad12..8ad8899216 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 @@ -59,6 +59,7 @@ import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; +import com.google.android.exoplayer2.ui.spherical.SingleTapListener; import com.google.android.exoplayer2.ui.spherical.SphericalSurfaceView; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ErrorMessageProvider; @@ -390,6 +391,7 @@ public class PlayerView extends FrameLayout { Assertions.checkState(Util.SDK_INT >= 15); SphericalSurfaceView sphericalSurfaceView = new SphericalSurfaceView(context); sphericalSurfaceView.setSurfaceListener(componentListener); + sphericalSurfaceView.setSingleTapListener(componentListener); surfaceView = sphericalSurfaceView; break; default: @@ -1021,15 +1023,10 @@ public class PlayerView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { - if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) { + if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { return false; } - if (!controller.isVisible()) { - maybeShowController(true); - } else if (controllerHideOnTouch) { - controller.hide(); - } - return true; + return toggleControllerVisibility(); } @Override @@ -1067,6 +1064,18 @@ public class PlayerView extends FrameLayout { } } + private boolean toggleControllerVisibility() { + if (!useController || player == null) { + return false; + } + if (!controller.isVisible()) { + maybeShowController(true); + } else if (controllerHideOnTouch) { + controller.hide(); + } + return true; + } + /** Shows the playback controls, but only if forced or shown indefinitely. */ private void maybeShowController(boolean isForced) { if (isPlayingAd() && controllerHideDuringAds) { @@ -1286,7 +1295,8 @@ public class PlayerView extends FrameLayout { TextOutput, VideoListener, OnLayoutChangeListener, - SphericalSurfaceView.SurfaceListener { + SphericalSurfaceView.SurfaceListener, + SingleTapListener { // TextOutput implementation @@ -1391,5 +1401,12 @@ public class PlayerView extends FrameLayout { } } } + + // SingleTapListener implementation + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return toggleControllerVisibility(); + } } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SingleTapListener.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SingleTapListener.java new file mode 100644 index 0000000000..7328bdfcab --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SingleTapListener.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui.spherical; + +import android.view.MotionEvent; + +/** Listens tap events on a {@link android.view.View}. */ +public interface SingleTapListener { + /** + * Notified when a tap occurs with the up {@link MotionEvent} that triggered it. + * + * @param e The up motion event that completed the first tap. + * @return Whether the event is consumed. + */ + boolean onSingleTapUp(MotionEvent e); +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index d90b1d31b3..483376dba4 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -91,6 +91,7 @@ public final class SphericalSurfaceView extends GLSurfaceView private final PhoneOrientationListener phoneOrientationListener; private final Renderer renderer; private final Handler mainHandler; + private final TouchTracker touchTracker; private @Nullable SurfaceListener surfaceListener; private @Nullable SurfaceTexture surfaceTexture; private @Nullable Surface surface; @@ -121,7 +122,7 @@ public final class SphericalSurfaceView extends GLSurfaceView renderer = new Renderer(); - TouchTracker touchTracker = new TouchTracker(renderer, PX_PER_DEGREES); + touchTracker = new TouchTracker(context, renderer, PX_PER_DEGREES); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay(); phoneOrientationListener = new PhoneOrientationListener(display, touchTracker, renderer); @@ -155,6 +156,11 @@ public final class SphericalSurfaceView extends GLSurfaceView surfaceListener = listener; } + /** Sets the {@link SingleTapListener} used to listen to single tap events on this view. */ + public void setSingleTapListener(@Nullable SingleTapListener listener) { + touchTracker.setSingleTapListener(listener); + } + @Override public void onVideoFrameAboutToBeRendered( long presentationTimeUs, long releaseTimeNs, Format format) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java index ea3a0b4e16..335f611b58 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java @@ -15,8 +15,11 @@ */ package com.google.android.exoplayer2.ui.spherical; +import android.content.Context; import android.graphics.PointF; import android.support.annotation.BinderThread; +import android.support.annotation.Nullable; +import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -42,7 +45,8 @@ import android.view.View; * Mesh as the user moves their finger. However, that requires quaternion interpolation. */ // @VisibleForTesting -/*package*/ class TouchTracker implements View.OnTouchListener { +/*package*/ class TouchTracker extends GestureDetector.SimpleOnGestureListener + implements View.OnTouchListener { /*package*/ interface Listener { void onScrollChange(PointF scrollOffsetDegrees); @@ -58,16 +62,27 @@ import android.view.View; private final Listener listener; private final float pxPerDegrees; + private final GestureDetector gestureDetector; // The conversion from touch to yaw & pitch requires compensating for device roll. This is set // on the sensor thread and read on the UI thread. private volatile float roll; + private @Nullable SingleTapListener singleTapListener; - public TouchTracker(Listener listener, float pxPerDegrees) { + @SuppressWarnings({ + "nullness:assignment.type.incompatible", + "nullness:argument.type.incompatible" + }) + public TouchTracker(Context context, Listener listener, float pxPerDegrees) { this.listener = listener; this.pxPerDegrees = pxPerDegrees; + gestureDetector = new GestureDetector(context, this); roll = SphericalSurfaceView.UPRIGHT_ROLL; } + public void setSingleTapListener(@Nullable SingleTapListener listener) { + singleTapListener = listener; + } + /** * Converts ACTION_MOVE events to pitch & yaw events while compensating for device roll. * @@ -75,36 +90,46 @@ import android.view.View; */ @Override public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - // Initialize drag gesture. - previousTouchPointPx.set(event.getX(), event.getY()); - return true; - case MotionEvent.ACTION_MOVE: - // Calculate the touch delta in screen space. - float touchX = (event.getX() - previousTouchPointPx.x) / pxPerDegrees; - float touchY = (event.getY() - previousTouchPointPx.y) / pxPerDegrees; - previousTouchPointPx.set(event.getX(), event.getY()); + return gestureDetector.onTouchEvent(event); + } - float r = roll; // Copy volatile state. - float cr = (float) Math.cos(r); - float sr = (float) Math.sin(r); - // To convert from screen space to the 3D space, we need to adjust the drag vector based - // on the roll of the phone. This is standard rotationMatrix(roll) * vector math but has - // an inverted y-axis due to the screen-space coordinates vs GL coordinates. - // Handle yaw. - accumulatedTouchOffsetDegrees.x -= cr * touchX - sr * touchY; - // Handle pitch and limit it to 45 degrees. - accumulatedTouchOffsetDegrees.y += sr * touchX + cr * touchY; - accumulatedTouchOffsetDegrees.y = - Math.max( - -MAX_PITCH_DEGREES, Math.min(MAX_PITCH_DEGREES, accumulatedTouchOffsetDegrees.y)); + @Override + public boolean onDown(MotionEvent e) { + // Initialize drag gesture. + previousTouchPointPx.set(e.getX(), e.getY()); + return true; + } - listener.onScrollChange(accumulatedTouchOffsetDegrees); - return true; - default: - return false; + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // Calculate the touch delta in screen space. + float touchX = (e2.getX() - previousTouchPointPx.x) / pxPerDegrees; + float touchY = (e2.getY() - previousTouchPointPx.y) / pxPerDegrees; + previousTouchPointPx.set(e2.getX(), e2.getY()); + + float r = roll; // Copy volatile state. + float cr = (float) Math.cos(r); + float sr = (float) Math.sin(r); + // To convert from screen space to the 3D space, we need to adjust the drag vector based + // on the roll of the phone. This is standard rotationMatrix(roll) * vector math but has + // an inverted y-axis due to the screen-space coordinates vs GL coordinates. + // Handle yaw. + accumulatedTouchOffsetDegrees.x -= cr * touchX - sr * touchY; + // Handle pitch and limit it to 45 degrees. + accumulatedTouchOffsetDegrees.y += sr * touchX + cr * touchY; + accumulatedTouchOffsetDegrees.y = + Math.max(-MAX_PITCH_DEGREES, Math.min(MAX_PITCH_DEGREES, accumulatedTouchOffsetDegrees.y)); + + listener.onScrollChange(accumulatedTouchOffsetDegrees); + return true; + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (singleTapListener != null) { + return singleTapListener.onSingleTapUp(e); } + return false; } @BinderThread From 2a9d5c21e27a13773864e40dac1543711979f5c0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 23 Aug 2018 04:18:25 -0700 Subject: [PATCH 26/42] Clarify doc for Player.getDuration and add Player.getContentDuration. Similar to getBufferedPosition and getCurrentPosition, getDuration should mention that it also returns the duration of the current ad. And, also similar to getContentBufferedDuration and getContentPosition, a new method getContentDuration simplifies querying the content duration while playing an ad. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209914696 --- .../android/exoplayer2/ext/cast/CastPlayer.java | 5 +++++ .../google/android/exoplayer2/ExoPlayerImpl.java | 16 +++++++++------- .../com/google/android/exoplayer2/Player.java | 11 +++++++++-- .../android/exoplayer2/SimpleExoPlayer.java | 5 +++++ .../exoplayer2/testutil/StubExoPlayer.java | 5 +++++ 5 files changed, 33 insertions(+), 9 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 f3e66315b4..65ae097452 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 @@ -567,6 +567,11 @@ public final class CastPlayer implements Player { return C.INDEX_UNSET; } + @Override + public long getContentDuration() { + return getDuration(); + } + @Override public boolean isLoading() { return false; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 7149cf5cc2..b663b1185c 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 @@ -494,18 +494,13 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public long getDuration() { - Timeline timeline = playbackInfo.timeline; - if (timeline.isEmpty()) { - return C.TIME_UNSET; - } if (isPlayingAd()) { MediaPeriodId periodId = playbackInfo.periodId; - timeline.getPeriodByUid(periodId.periodUid, period); + playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); return C.usToMs(adDurationUs); - } else { - return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); } + return getContentDuration(); } @Override @@ -570,6 +565,13 @@ import java.util.concurrent.CopyOnWriteArraySet; return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } + @Override + public long getContentDuration() { + return playbackInfo.timeline.isEmpty() + ? C.TIME_UNSET + : playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } + @Override public long getContentPosition() { if (isPlayingAd()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index a00af72485..bb51cde793 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -748,8 +748,8 @@ public interface Player { @Nullable Object getCurrentTag(); /** - * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the - * duration is not known. + * Returns the duration of the current content window or ad in milliseconds, or {@link + * C#TIME_UNSET} if the duration is not known. */ long getDuration(); @@ -807,6 +807,13 @@ public interface Player { */ int getCurrentAdIndexInAdGroup(); + /** + * If {@link #isPlayingAd()} returns {@code true}, returns the duration of the current content + * window 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()}. + */ + long getContentDuration(); + /** * 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 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 7123e4cc91..de150ddfe3 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 @@ -1053,6 +1053,11 @@ public class SimpleExoPlayer return player.getCurrentAdIndexInAdGroup(); } + @Override + public long getContentDuration() { + return player.getContentDuration(); + } + @Override public long getContentPosition() { return player.getContentPosition(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 246e2918c4..6c99da55cc 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -299,6 +299,11 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public long getContentDuration() { + throw new UnsupportedOperationException(); + } + @Override public long getContentPosition() { throw new UnsupportedOperationException(); From 4d8a5c44b31a8b3865357d2a94cf989a2e001e54 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 23 Aug 2018 09:17:40 -0700 Subject: [PATCH 27/42] Fix some more lint issues ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209947034 --- .../java/com/google/android/exoplayer2/Player.java | 6 +++--- .../java/com/google/android/exoplayer2/Renderer.java | 2 +- .../com/google/android/exoplayer2/drm/DrmSession.java | 2 +- .../android/exoplayer2/drm/FrameworkMediaDrm.java | 2 +- .../google/android/exoplayer2/extractor/Extractor.java | 2 +- .../java/com/google/android/exoplayer2/util/Util.java | 4 +++- .../source/ConcatenatingMediaSourceTest.java | 10 +++++----- .../exoplayer2/testutil/FakeAdaptiveMediaPeriod.java | 9 +++++++-- 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index bb51cde793..0e4cf68aa4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -430,7 +430,7 @@ public interface Player { */ @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) - public @interface RepeatMode {} + @interface RepeatMode {} /** * Normal playback without repetition. */ @@ -457,7 +457,7 @@ public interface Player { DISCONTINUITY_REASON_AD_INSERTION, DISCONTINUITY_REASON_INTERNAL }) - public @interface DiscontinuityReason {} + @interface DiscontinuityReason {} /** * Automatic playback transition from one period in the timeline to the next. The period index may * be the same as it was before the discontinuity in case the current period is repeated. @@ -485,7 +485,7 @@ public interface Player { TIMELINE_CHANGE_REASON_RESET, TIMELINE_CHANGE_REASON_DYNAMIC }) - public @interface TimelineChangeReason {} + @interface TimelineChangeReason {} /** * Timeline and manifest changed as a result of a player initialization with new media. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index d56b2f4f7b..d1e1541cdc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -40,7 +40,7 @@ public interface Renderer extends PlayerMessage.Target { */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) - public @interface State {} + @interface State {} /** * The renderer is disabled. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index 40bbde4d8a..bed3545d78 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -45,7 +45,7 @@ public interface DrmSession { */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) - public @interface State {} + @interface State {} /** * The session has been released. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 44a31a1896..548f9cb669 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -227,7 +227,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrmsneakyThrowInternal(t); + Util.sneakyThrowInternal(t); } @SuppressWarnings("unchecked") @@ -1749,6 +1750,7 @@ public final class Util { // Attempt to read sys.display-size. String sysDisplaySize = null; try { + @SuppressLint("PrivateApi") Class systemProperties = Class.forName("android.os.SystemProperties"); Method getMethod = systemProperties.getMethod("get", String.class); sysDisplaySize = (String) getMethod.invoke(systemProperties, "sys.display-size"); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 3ac962f6c3..507b718e8f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -97,7 +97,7 @@ public final class ConcatenatingMediaSourceTest { // Add bulk. mediaSource.addMediaSources( - 3, Arrays.asList(childSources[4], childSources[5], childSources[6])); + 3, Arrays.asList(childSources[4], childSources[5], childSources[6])); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333); @@ -375,7 +375,7 @@ public final class ConcatenatingMediaSourceTest { }; Timeline nonEmptyTimeline = new FakeTimeline(/* windowCount = */ 1); - mediaSource.addMediaSources(Arrays.asList(childSources)); + mediaSource.addMediaSources(Arrays.asList(childSources)); Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertEmpty(timeline); @@ -642,7 +642,7 @@ public final class ConcatenatingMediaSourceTest { ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(/* isAtomic= */ true, new FakeShuffleOrder(0)); testRunner = new MediaSourceTestRunner(mediaSource, null); - mediaSource.addMediaSources(Arrays.asList(createMediaSources(3))); + mediaSource.addMediaSources(Arrays.asList(createMediaSources(3))); Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); @@ -747,7 +747,7 @@ public final class ConcatenatingMediaSourceTest { mediaSource.addMediaSource(childSource); mediaSource.addMediaSource(childSource); testRunner.prepareSource(); - mediaSource.addMediaSources(Arrays.asList(childSource, childSource)); + mediaSource.addMediaSources(Arrays.asList(childSource, childSource)); Timeline timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1); @@ -780,7 +780,7 @@ public final class ConcatenatingMediaSourceTest { testRunner.prepareSource(); mediaSource.addMediaSources( - Arrays.asList(childSource, nestedConcatenation, nestedConcatenation)); + Arrays.asList(childSource, nestedConcatenation, nestedConcatenation)); testRunner.assertTimelineChangeBlocking(); nestedConcatenation.addMediaSource(childSource); testRunner.assertTimelineChangeBlocking(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index 6218e4624d..70c40b76e9 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -76,8 +76,13 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod } @Override - public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, - SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + @SuppressWarnings("unchecked") + public long selectTracks( + TrackSelection[] selections, + boolean[] mayRetainStreamFlags, + SampleStream[] streams, + boolean[] streamResetFlags, + long positionUs) { long returnPositionUs = super.selectTracks(selections, mayRetainStreamFlags, streams, streamResetFlags, positionUs); List> validStreams = new ArrayList<>(); From 24d04a26e43972629182b9676677d404c073d92d Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 23 Aug 2018 10:27:24 -0700 Subject: [PATCH 28/42] Add support for variable substition in HLS Issue:#4422 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209958623 --- RELEASENOTES.md | 2 + .../hls/playlist/HlsMasterPlaylist.java | 17 +- .../hls/playlist/HlsPlaylistParser.java | 149 +++++++++++++----- .../playlist/HlsMasterPlaylistParserTest.java | 18 +++ .../playlist/HlsMediaPlaylistParserTest.java | 65 +++++++- 5 files changed, 204 insertions(+), 47 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 23950c958f..99c368a671 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -70,6 +70,8 @@ * Allow configuration of the Loader retry delay ([#3370](https://github.com/google/ExoPlayer/issues/3370)). * HLS: + * Add support for variable substitution + ([#4422](https://github.com/google/ExoPlayer/issues/4422)). * Add support for PlayReady. * Add support for alternative EXT-X-KEY tags. * Set the bitrate on primary track sample formats diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index c45c2dd547..bb01ade28d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; /** Represents an HLS master playlist. */ public final class HlsMasterPlaylist extends HlsPlaylist { @@ -35,7 +36,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { /* subtitles= */ Collections.emptyList(), /* muxedAudioFormat= */ null, /* muxedCaptionFormats= */ Collections.emptyList(), - /* hasIndependentSegments= */ false); + /* hasIndependentSegments= */ false, + /* variableDefinitions= */ Collections.emptyMap()); public static final int GROUP_INDEX_VARIANT = 0; public static final int GROUP_INDEX_AUDIO = 1; @@ -110,6 +112,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { * captions information. */ public final List muxedCaptionFormats; + /** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */ + public final Map variableDefinitions; /** * @param baseUri See {@link #baseUri}. @@ -120,6 +124,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { * @param muxedAudioFormat See {@link #muxedAudioFormat}. * @param muxedCaptionFormats See {@link #muxedCaptionFormats}. * @param hasIndependentSegments See {@link #hasIndependentSegments}. + * @param variableDefinitions See {@link #variableDefinitions}. */ public HlsMasterPlaylist( String baseUri, @@ -129,7 +134,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { List subtitles, Format muxedAudioFormat, List muxedCaptionFormats, - boolean hasIndependentSegments) { + boolean hasIndependentSegments, + Map variableDefinitions) { super(baseUri, tags, hasIndependentSegments); this.variants = Collections.unmodifiableList(variants); this.audios = Collections.unmodifiableList(audios); @@ -137,6 +143,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { this.muxedAudioFormat = muxedAudioFormat; this.muxedCaptionFormats = muxedCaptionFormats != null ? Collections.unmodifiableList(muxedCaptionFormats) : null; + this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions); } @Override @@ -149,7 +156,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { copyRenditionsList(subtitles, GROUP_INDEX_SUBTITLE, streamKeys), muxedAudioFormat, muxedCaptionFormats, - hasIndependentSegments); + hasIndependentSegments, + variableDefinitions); } /** @@ -169,7 +177,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { emptyList, /* muxedAudioFormat= */ null, /* muxedCaptionFormats= */ null, - /* hasIndependentSegments= */ false); + /* hasIndependentSegments= */ false, + /* variableDefinitions= */ Collections.emptyMap()); } private static List copyRenditionsList( diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index e287b5220e..49826902cd 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -40,6 +40,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.TreeMap; import java.util.regex.Matcher; @@ -57,6 +58,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variantUrls = new HashSet<>(); HashMap audioGroupIdToCodecs = new HashMap<>(); + HashMap variableDefinitions = new HashMap<>(); ArrayList variants = new ArrayList<>(); ArrayList audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); @@ -258,7 +265,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions = new HashMap<>(); List segments = new ArrayList<>(); List tags = new ArrayList<>(); @@ -465,7 +483,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser 1) { @@ -587,7 +620,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions) throws ParserException { + String keyFormatVersions = + parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions); if (!"1".equals(keyFormatVersions)) { // Not supported. return null; } - String uriString = parseStringAttr(line, REGEX_URI); + String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT); byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data); return new SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData); } - private static @Nullable SchemeData parseWidevineSchemeData(String line, String keyFormat) + private static @Nullable SchemeData parseWidevineSchemeData( + String line, String keyFormat, Map variableDefinitions) throws ParserException { if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) { - String uriString = parseStringAttr(line, REGEX_URI); - return new SchemeData(C.WIDEVINE_UUID, MimeTypes.VIDEO_MP4, - Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT)); + String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); + return new SchemeData( + C.WIDEVINE_UUID, + MimeTypes.VIDEO_MP4, + Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT)); } if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) { try { @@ -657,19 +695,21 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions) + throws ParserException { + String value = parseOptionalStringAttr(line, pattern, variableDefinitions); if (value != null) { return value; } else { @@ -677,14 +717,39 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions) { + return parseOptionalStringAttr(line, pattern, null, variableDefinitions); } private static @PolyNull String parseOptionalStringAttr( - String line, Pattern pattern, @PolyNull String defaultValue) { + String line, + Pattern pattern, + @PolyNull String defaultValue, + Map variableDefinitions) { Matcher matcher = pattern.matcher(line); - return matcher.find() ? matcher.group(1) : defaultValue; + String value = matcher.find() ? matcher.group(1) : defaultValue; + return variableDefinitions.isEmpty() || value == null + ? value + : replaceVariableReferences(value, variableDefinitions); + } + + private static String replaceVariableReferences( + String string, Map variableDefinitions) { + Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string); + // TODO: Replace StringBuffer with StringBuilder once Java 9 is available. + StringBuffer stringWithReplacements = new StringBuffer(); + while (matcher.find()) { + String groupName = matcher.group(1); + if (variableDefinitions.containsKey(groupName)) { + matcher.appendReplacement( + stringWithReplacements, Matcher.quoteReplacement(variableDefinitions.get(groupName))); + } else { + // The variable is not defined. The value is ignored. + } + } + matcher.appendTail(stringWithReplacements); + return stringWithReplacements.toString(); } private static boolean parseOptionalBooleanAttribute( diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index 11fef3c844..d818111eec 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -117,6 +117,15 @@ public class HlsMasterPlaylistParserTest { + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n" + "http://example.com/spaces_in_codecs.m3u8\n"; + private static final String PLAYLIST_WITH_VARIABLE_SUBSTITUTION = + " #EXTM3U \n" + + "\n" + + "#EXT-X-DEFINE:NAME=\"codecs\",VALUE=\"mp4a.40.5\"\n" + + "#EXT-X-DEFINE:NAME=\"tricky\",VALUE=\"This/{$nested}/reference/shouldnt/work\"\n" + + "#EXT-X-DEFINE:NAME=\"nested\",VALUE=\"This should not be inserted\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"{$codecs}\"\n" + + "http://example.com/{$tricky}\n"; + @Test public void testParseMasterPlaylist() throws IOException { HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); @@ -218,6 +227,15 @@ public class HlsMasterPlaylistParserTest { assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse(); } + @Test + public void testVariableSubstitution() throws IOException { + HlsMasterPlaylist playlistWithSubstitutions = + parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION); + HlsMasterPlaylist.HlsUrl variant = playlistWithSubstitutions.variants.get(0); + assertThat(variant.format.codecs).isEqualTo("mp4a.40.5"); + assertThat(variant.url).isEqualTo("http://example.com/This/{$nested}/reference/shouldnt/work"); + } + private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) throws IOException { Uri playlistUri = Uri.parse(uri); diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 6e71aebb74..e7bf3c6324 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; +import java.util.HashMap; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -397,9 +398,71 @@ public class HlsMediaPlaylistParserTest { /* subtitles= */ Collections.emptyList(), /* muxedAudioFormat= */ null, /* muxedCaptionFormats= */ null, - /* hasIndependentSegments= */ true); + /* hasIndependentSegments= */ true, + /* variableDefinitions */ Collections.emptyMap()); HlsMediaPlaylist playlistWithInheritance = (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); assertThat(playlistWithInheritance.hasIndependentSegments).isTrue(); } + + @Test + public void testVariableSubstitution() throws IOException { + Uri playlistUri = Uri.parse("https://example.com/substitution.m3u8"); + String playlistString = + "#EXTM3U\n" + + "#EXT-X-VERSION:8\n" + + "#EXT-X-DEFINE:NAME=\"underscore_1\",VALUE=\"{\"\n" + + "#EXT-X-DEFINE:NAME=\"dash-1\",VALUE=\"replaced_value.ts\"\n" + + "#EXT-X-TARGETDURATION:5\n" + + "#EXT-X-MEDIA-SEQUENCE:10\n" + + "#EXTINF:5.005,\n" + + "segment1.ts\n" + + "#EXT-X-MAP:URI=\"{$dash-1}\"" + + "#EXTINF:5.005,\n" + + "segment{$underscore_1}$name_1}\n"; + InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); + HlsMediaPlaylist playlist = + (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); + Segment segment = playlist.segments.get(1); + assertThat(segment.initializationSegment.url).isEqualTo("replaced_value.ts"); + assertThat(segment.url).isEqualTo("segment{$name_1}"); + } + + @Test + public void testInheritedVariableSubstitution() throws IOException { + Uri playlistUri = Uri.parse("https://example.com/test3.m3u8"); + String playlistString = + "#EXTM3U\n" + + "#EXT-X-VERSION:8\n" + + "#EXT-X-TARGETDURATION:5\n" + + "#EXT-X-MEDIA-SEQUENCE:10\n" + + "#EXT-X-DEFINE:IMPORT=\"imported_base\"\n" + + "#EXTINF:5.005,\n" + + "{$imported_base}1.ts\n" + + "#EXTINF:5.005,\n" + + "{$imported_base}2.ts\n" + + "#EXTINF:5.005,\n" + + "{$imported_base}3.ts\n" + + "#EXTINF:5.005,\n" + + "{$imported_base}4.ts\n"; + InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); + HashMap variableDefinitions = new HashMap<>(); + variableDefinitions.put("imported_base", "long_path"); + HlsMasterPlaylist masterPlaylist = + new HlsMasterPlaylist( + /* baseUri= */ "", + /* tags= */ Collections.emptyList(), + /* variants= */ Collections.emptyList(), + /* audios= */ Collections.emptyList(), + /* subtitles= */ Collections.emptyList(), + /* muxedAudioFormat= */ null, + /* muxedCaptionFormats= */ Collections.emptyList(), + /* hasIndependentSegments= */ false, + variableDefinitions); + HlsMediaPlaylist playlist = + (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); + for (int i = 1; i <= 4; i++) { + assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts"); + } + } } From 9c3677360214d783b2d529f8bbb867fa8c7b3819 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 24 Aug 2018 02:12:13 -0700 Subject: [PATCH 29/42] Fix playback of postrolls with multiple ads At the point of starting to play a postroll, source info refreshes for future postroll ads in the same ad group would cause a seek that incorrectly identified the media period to play as the content media period. Fix the logic in getAdGroupIndexForPositionUs to address this. Also handle empty postroll ad breaks by resetting the expected ad group index when we send content complete. Issue: #4710 Issue: #4681 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210071054 --- RELEASENOTES.md | 8 ++++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 43 ++++++++++--------- .../source/ads/AdPlaybackState.java | 12 +++++- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 99c368a671..29d7b4dcaa 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -118,6 +118,14 @@ * Add option to show buffering view when playWhenReady is false ([#4304](https://github.com/google/ExoPlayer/issues/4304)). * Allow any `Drawable` to be used as `PlayerView` default artwork. +* IMA: + * Refine the previous fix for empty ad groups to avoid discarding ad breaks + unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030)), + ([#4280](https://github.com/google/ExoPlayer/issues/4280)). + * Fix handling of empty postrolls + ([#4681](https://github.com/google/ExoPlayer/issues/4681). + * Fix handling of postrolls with multiple ads + ([#4710](https://github.com/google/ExoPlayer/issues/4710). ### 2.8.4 ### diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 649e6e386a..56d62f26a9 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -864,8 +864,6 @@ public final class ImaAdsLoader && playWhenReady) { checkForContentComplete(); } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) { - // IMA is waiting for the ad playback to finish so invoke the callback now. - // Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again. for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onEnded(); } @@ -1041,26 +1039,24 @@ public final class ImaAdsLoader int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup; playingAd = player.isPlayingAd(); playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET; - if (!sentContentComplete) { - boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup; - if (adFinished) { - // IMA is waiting for the ad playback to finish so invoke the callback now. - // Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again. - for (int i = 0; i < adCallbacks.size(); i++) { - adCallbacks.get(i).onEnded(); - } - if (DEBUG) { - Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); - } + boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup; + if (adFinished) { + // IMA is waiting for the ad playback to finish so invoke the callback now. + // Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again. + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onEnded(); } - if (!wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { - int adGroupIndex = player.getCurrentAdGroupIndex(); - // IMA hasn't called playAd yet, so fake the content position. - fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); - if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { - fakeContentProgressOffsetMs = contentDurationMs; - } + if (DEBUG) { + Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); + } + } + if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { + int adGroupIndex = player.getCurrentAdGroupIndex(); + // IMA hasn't called playAd yet, so fake the content position. + fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); + fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { + fakeContentProgressOffsetMs = contentDurationMs; } } } @@ -1125,6 +1121,7 @@ public final class ImaAdsLoader pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex); } pendingContentPositionMs = C.TIME_UNSET; + fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; } private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { @@ -1172,6 +1169,10 @@ public final class ImaAdsLoader Log.d(TAG, "adsLoader.contentComplete"); } sentContentComplete = true; + // After sending content complete IMA will not poll the content position, so set the expected + // ad group index. + expectedAdGroupIndex = + adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentDurationMs)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index bbcba3aab5..72fc162bc3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -315,8 +315,7 @@ public final class AdPlaybackState { // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. // In practice we expect there to be few ad groups so the search shouldn't be expensive. int index = adGroupTimesUs.length - 1; - while (index >= 0 - && (adGroupTimesUs[index] == C.TIME_END_OF_SOURCE || adGroupTimesUs[index] > positionUs)) { + while (index >= 0 && isPositionBeforeAdGroup(positionUs, index)) { index--; } return index >= 0 && adGroups[index].hasUnplayedAds() ? index : C.INDEX_UNSET; @@ -454,4 +453,13 @@ public final class AdPlaybackState { result = 31 * result + Arrays.hashCode(adGroups); return result; } + + private boolean isPositionBeforeAdGroup(long positionUs, int adGroupIndex) { + long adGroupPositionUs = adGroupTimesUs[adGroupIndex]; + if (adGroupPositionUs == C.TIME_END_OF_SOURCE) { + return contentDurationUs == C.TIME_UNSET || positionUs < contentDurationUs; + } else { + return positionUs < adGroupPositionUs; + } + } } From a71e28440d2827d202d206a80d1d9c68ad731dbe Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 24 Aug 2018 02:40:21 -0700 Subject: [PATCH 30/42] Add parameter to force disable adaptive track selection. This option is currently not available as a non-null adaptive track selection has to be provided. Adds a parameter "forceHighestSupportedBitrate" which corresponds to the default fixed track selection for audio and video. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210073675 --- .../trackselection/DefaultTrackSelector.java | 33 +++++++++++++++++-- .../DefaultTrackSelectorTest.java | 1 + 2 files changed, 32 insertions(+), 2 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 87c4a50fd5..fbdbb8d95f 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 @@ -168,6 +168,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private boolean selectUndeterminedTextLanguage; private int disabledTextTrackSelectionFlags; private boolean forceLowestBitrate; + private boolean forceHighestSupportedBitrate; private boolean allowMixedMimeAdaptiveness; private boolean allowNonSeamlessAdaptiveness; private int maxVideoWidth; @@ -197,6 +198,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { selectUndeterminedTextLanguage = initialValues.selectUndeterminedTextLanguage; disabledTextTrackSelectionFlags = initialValues.disabledTextTrackSelectionFlags; forceLowestBitrate = initialValues.forceLowestBitrate; + forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; allowMixedMimeAdaptiveness = initialValues.allowMixedMimeAdaptiveness; allowNonSeamlessAdaptiveness = initialValues.allowNonSeamlessAdaptiveness; maxVideoWidth = initialValues.maxVideoWidth; @@ -262,6 +264,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * See {@link Parameters#forceHighestSupportedBitrate}. + * + * @return This builder. + */ + public ParametersBuilder setForceHighestSupportedBitrate(boolean forceHighestSupportedBitrate) { + this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; + return this; + } + /** * See {@link Parameters#allowMixedMimeAdaptiveness}. * @@ -520,6 +532,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { selectUndeterminedTextLanguage, disabledTextTrackSelectionFlags, forceLowestBitrate, + forceHighestSupportedBitrate, allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, @@ -634,6 +647,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { * with all other constraints. The default value is {@code false}. */ public final boolean forceLowestBitrate; + /** + * Whether to force selection of the highest bitrate audio and video tracks that comply with all + * other constraints. The default value is {@code false}. + */ + public final boolean forceHighestSupportedBitrate; /** * Whether to allow adaptive selections containing mixed mime types. The default value is {@code * false}. @@ -670,6 +688,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { /* selectUndeterminedTextLanguage= */ false, /* disabledTextTrackSelectionFlags= */ 0, /* forceLowestBitrate= */ false, + /* forceHighestSupportedBitrate= */ false, /* allowMixedMimeAdaptiveness= */ false, /* allowNonSeamlessAdaptiveness= */ true, /* maxVideoWidth= */ Integer.MAX_VALUE, @@ -691,6 +710,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean selectUndeterminedTextLanguage, int disabledTextTrackSelectionFlags, boolean forceLowestBitrate, + boolean forceHighestSupportedBitrate, boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness, int maxVideoWidth, @@ -709,6 +729,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; this.disabledTextTrackSelectionFlags = disabledTextTrackSelectionFlags; this.forceLowestBitrate = forceLowestBitrate; + this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; this.maxVideoWidth = maxVideoWidth; @@ -730,6 +751,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.selectUndeterminedTextLanguage = Util.readBoolean(in); this.disabledTextTrackSelectionFlags = in.readInt(); this.forceLowestBitrate = Util.readBoolean(in); + this.forceHighestSupportedBitrate = Util.readBoolean(in); this.allowMixedMimeAdaptiveness = Util.readBoolean(in); this.allowNonSeamlessAdaptiveness = Util.readBoolean(in); this.maxVideoWidth = in.readInt(); @@ -797,6 +819,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { return selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage && disabledTextTrackSelectionFlags == other.disabledTextTrackSelectionFlags && forceLowestBitrate == other.forceLowestBitrate + && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate && allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness && maxVideoWidth == other.maxVideoWidth @@ -819,6 +842,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { int result = selectUndeterminedTextLanguage ? 1 : 0; result = 31 * result + disabledTextTrackSelectionFlags; result = 31 * result + (forceLowestBitrate ? 1 : 0); + result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); result = 31 * result + maxVideoWidth; @@ -852,6 +876,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { Util.writeBoolean(dest, selectUndeterminedTextLanguage); dest.writeInt(disabledTextTrackSelectionFlags); Util.writeBoolean(dest, forceLowestBitrate); + Util.writeBoolean(dest, forceHighestSupportedBitrate); Util.writeBoolean(dest, allowMixedMimeAdaptiveness); Util.writeBoolean(dest, allowNonSeamlessAdaptiveness); dest.writeInt(maxVideoWidth); @@ -1355,7 +1380,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Nullable TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { TrackSelection selection = null; - if (!params.forceLowestBitrate && adaptiveTrackSelectionFactory != null) { + if (!params.forceHighestSupportedBitrate + && !params.forceLowestBitrate + && adaptiveTrackSelectionFactory != null) { selection = selectAdaptiveVideoTrack( groups, @@ -1606,7 +1633,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { } TrackGroup selectedGroup = groups.get(selectedGroupIndex); - if (!params.forceLowestBitrate && adaptiveTrackSelectionFactory != null) { + if (!params.forceHighestSupportedBitrate + && !params.forceLowestBitrate + && adaptiveTrackSelectionFactory != null) { // If the group of the track with the highest score allows it, try to enable adaptation. int[] adaptiveTracks = getAdaptiveAudioTracks( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 13314dccf0..86d810989f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -147,6 +147,7 @@ public final class DefaultTrackSelectorTest { /* selectUndeterminedTextLanguage= */ false, /* disabledTextTrackSelectionFlags= */ 0, /* forceLowestBitrate= */ true, + /* forceHighestSupportedBitrate= */ true, /* allowMixedMimeAdaptiveness= */ false, /* allowNonSeamlessAdaptiveness= */ true, /* maxVideoWidth= */ 1, From 38f2f352f9bbed34c77d8d7a8197f64d0de6d6e8 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 03:31:11 -0700 Subject: [PATCH 31/42] Move playback tests off deprecated APIs ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210077548 --- .../dash/offline/DashDownloaderTest.java | 2 +- .../dash/offline/DownloadManagerDashTest.java | 3 +- .../dash/offline/DownloadServiceDashTest.java | 2 +- .../source/hls/offline/HlsDownloaderTest.java | 2 +- .../playbacktests/gts/DashTestRunner.java | 37 +++++---- .../exoplayer2/testutil/ExoHostedTest.java | 76 ++++++------------- .../testutil/ExoPlayerTestRunner.java | 66 ++++++---------- .../testutil/FakeAdaptiveMediaSource.java | 5 -- .../exoplayer2/testutil/FakeDataSource.java | 13 +--- 9 files changed, 71 insertions(+), 135 deletions(-) diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java index 841da07114..a597d780e0 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java @@ -298,7 +298,7 @@ public class DashDownloaderTest { } private DashDownloader getDashDownloader(FakeDataSet fakeDataSet, StreamKey... keys) { - return getDashDownloader(new Factory(null).setFakeDataSet(fakeDataSet), keys); + return getDashDownloader(new Factory().setFakeDataSet(fakeDataSet), keys); } private DashDownloader getDashDownloader(Factory factory, StreamKey... keys) { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java index d161c91b1d..88d4ed6a9d 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java @@ -235,8 +235,7 @@ public class DownloadManagerDashTest { private void createDownloadManager() { dummyMainThread.runOnMainThread( () -> { - Factory fakeDataSourceFactory = - new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); + Factory fakeDataSourceFactory = new FakeDataSource.Factory().setFakeDataSet(fakeDataSet); downloadManager = new DownloadManager( new DownloaderConstructorHelper(cache, fakeDataSourceFactory), diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index 085e0fc555..70a64f5524 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -101,7 +101,7 @@ public class DownloadServiceDashTest { .setRandomData("text_segment_2", 2) .setRandomData("text_segment_3", 3); final DataSource.Factory fakeDataSourceFactory = - new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); + new FakeDataSource.Factory().setFakeDataSet(fakeDataSet); fakeStreamKey1 = new StreamKey(0, 0, 0); fakeStreamKey2 = new StreamKey(0, 1, 0); diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index b5ecad5b36..825988994e 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -183,7 +183,7 @@ public class HlsDownloaderTest { } private HlsDownloader getHlsDownloader(String mediaPlaylistUri, List keys) { - Factory factory = new Factory(null).setFakeDataSet(fakeDataSet); + Factory factory = new Factory().setFakeDataSet(fakeDataSet); return new HlsDownloader( Uri.parse(mediaPlaylistUri), keys, new DownloaderConstructorHelper(cache, factory)); } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index 0da9430b12..2caf1d2231 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -25,6 +25,7 @@ import android.net.Uri; import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; @@ -42,7 +43,6 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.DebugRenderersFactory; import com.google.android.exoplayer2.testutil.DecoderCountersUtil; @@ -55,11 +55,10 @@ import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; -import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; @@ -265,8 +264,7 @@ public final class DashTestRunner { } @Override - protected DefaultTrackSelector buildTrackSelector( - HostActivity host, BandwidthMeter bandwidthMeter) { + protected DefaultTrackSelector buildTrackSelector(HostActivity host) { return trackSelector; } @@ -296,30 +294,31 @@ public final class DashTestRunner { } @Override - protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, + protected SimpleExoPlayer buildExoPlayer( + HostActivity host, + Surface surface, MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance( - host, new DebugRenderersFactory(host), trackSelector, drmSessionManager); + host, + new DebugRenderersFactory(host), + trackSelector, + new DefaultLoadControl(), + drmSessionManager); player.setVideoSurface(surface); return player; } @Override - protected MediaSource buildSource( - HostActivity host, String userAgent, TransferListener mediaTransferListener) { - DataSource.Factory manifestDataSourceFactory = dataSourceFactory != null - ? dataSourceFactory : new DefaultDataSourceFactory(host, userAgent); - DataSource.Factory mediaDataSourceFactory = dataSourceFactory != null - ? dataSourceFactory - : new DefaultDataSourceFactory(host, userAgent, mediaTransferListener); + protected MediaSource buildSource(HostActivity host, String userAgent) { + DataSource.Factory dataSourceFactory = + this.dataSourceFactory != null + ? this.dataSourceFactory + : new DefaultDataSourceFactory(host, userAgent); Uri manifestUri = Uri.parse(manifestUrl); - DefaultDashChunkSource.Factory chunkSourceFactory = new DefaultDashChunkSource.Factory( - mediaDataSourceFactory); - return new DashMediaSource.Factory(chunkSourceFactory, manifestDataSourceFactory) - .setMinLoadableRetryCount(MIN_LOADABLE_RETRY_COUNT) - .setLivePresentationDelayMs(0) + return new DashMediaSource.Factory(dataSourceFactory) + .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MIN_LOADABLE_RETRY_COUNT)) .createMediaSource(manifestUri); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java index cf8d694286..3c6478c10a 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java @@ -22,6 +22,7 @@ import android.os.Looper; import android.os.SystemClock; import android.util.Log; import android.view.Surface; +import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -30,6 +31,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.decoder.DecoderCounters; @@ -40,9 +42,6 @@ import com.google.android.exoplayer2.testutil.HostActivity.HostedTest; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.Util; @@ -80,9 +79,7 @@ public abstract class ExoHostedTest private SimpleExoPlayer player; private Surface surface; private ExoPlaybackException playerError; - private Player.EventListener playerEventListener; - private VideoRendererEventListener videoDebugListener; - private AudioRendererEventListener audioDebugListener; + private AnalyticsListener analyticsListener; private boolean playerWasPrepared; private boolean playing; @@ -135,33 +132,11 @@ public abstract class ExoHostedTest } } - /** - * Sets an {@link Player.EventListener} to listen for ExoPlayer events during the test. - */ - public final void setEventListener(Player.EventListener eventListener) { - this.playerEventListener = eventListener; + /** Sets an {@link AnalyticsListener} to listen for events during the test. */ + public final void setAnalyticsListener(AnalyticsListener analyticsListener) { + this.analyticsListener = analyticsListener; if (player != null) { - player.addListener(eventListener); - } - } - - /** - * Sets an {@link VideoRendererEventListener} to listen for video debug events during the test. - */ - public final void setVideoDebugListener(VideoRendererEventListener videoDebugListener) { - this.videoDebugListener = videoDebugListener; - if (player != null) { - player.addVideoDebugListener(videoDebugListener); - } - } - - /** - * Sets an {@link AudioRendererEventListener} to listen for audio debug events during the test. - */ - public final void setAudioDebugListener(AudioRendererEventListener audioDebugListener) { - this.audioDebugListener = audioDebugListener; - if (player != null) { - player.addAudioDebugListener(audioDebugListener); + player.addAnalyticsListener(analyticsListener); } } @@ -171,20 +146,13 @@ public abstract class ExoHostedTest public final void onStart(HostActivity host, Surface surface) { this.surface = surface; // Build the player. - DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); - trackSelector = buildTrackSelector(host, bandwidthMeter); + trackSelector = buildTrackSelector(host); String userAgent = "ExoPlayerPlaybackTests"; DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); player = buildExoPlayer(host, surface, trackSelector, drmSessionManager); - player.prepare(buildSource(host, Util.getUserAgent(host, userAgent), bandwidthMeter)); - if (playerEventListener != null) { - player.addListener(playerEventListener); - } - if (videoDebugListener != null) { - player.addVideoDebugListener(videoDebugListener); - } - if (audioDebugListener != null) { - player.addAudioDebugListener(audioDebugListener); + player.prepare(buildSource(host, Util.getUserAgent(host, userAgent))); + if (analyticsListener != null) { + player.addAnalyticsListener(analyticsListener); } player.addListener(this); player.addAudioDebugListener(this); @@ -354,26 +322,30 @@ public abstract class ExoHostedTest } @SuppressWarnings("unused") - protected DefaultTrackSelector buildTrackSelector( - HostActivity host, BandwidthMeter bandwidthMeter) { - return new DefaultTrackSelector(new AdaptiveTrackSelection.Factory(bandwidthMeter)); + protected DefaultTrackSelector buildTrackSelector(HostActivity host) { + return new DefaultTrackSelector(new AdaptiveTrackSelection.Factory()); } @SuppressWarnings("unused") - protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, + protected SimpleExoPlayer buildExoPlayer( + HostActivity host, + Surface surface, MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { - RenderersFactory renderersFactory = new DefaultRenderersFactory(host, drmSessionManager, - DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, 0); + RenderersFactory renderersFactory = + new DefaultRenderersFactory( + host, + DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, + /* allowedVideoJoiningTimeMs= */ 0); SimpleExoPlayer player = - ExoPlayerFactory.newSimpleInstance(host, renderersFactory, trackSelector); + ExoPlayerFactory.newSimpleInstance( + host, renderersFactory, trackSelector, new DefaultLoadControl(), drmSessionManager); player.setVideoSurface(surface); return player; } @SuppressWarnings("unused") - protected abstract MediaSource buildSource( - HostActivity host, String userAgent, TransferListener mediaTransferListener); + protected abstract MediaSource buildSource(HostActivity host, String userAgent); @SuppressWarnings("unused") protected void onPlayerErrorInternal(ExoPlaybackException error) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index 2a50b2a240..b613f7f364 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -32,17 +32,16 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; -import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.CountDownLatch; @@ -77,13 +76,12 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc private MediaSource mediaSource; private DefaultTrackSelector trackSelector; private LoadControl loadControl; + private BandwidthMeter bandwidthMeter; private Format[] supportedFormats; private Renderer[] renderers; private RenderersFactory renderersFactory; private ActionSchedule actionSchedule; private Player.EventListener eventListener; - private VideoRendererEventListener videoRendererEventListener; - private AudioRendererEventListener audioRendererEventListener; private AnalyticsListener analyticsListener; private Integer expectedPlayerEndedCount; @@ -158,6 +156,18 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc return this; } + /** + * Sets the {@link BandwidthMeter} to be used by the test runner. The default value is a {@link + * DefaultBandwidthMeter} in its default configuration. + * + * @param bandwidthMeter The {@link BandwidthMeter} to be used by the test runner. + * @return This builder. + */ + public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) { + this.bandwidthMeter = bandwidthMeter; + return this; + } + /** * Sets a list of {@link Format}s to be used by a {@link FakeMediaSource} to create media * periods and for setting up a {@link FakeRenderer}. The default value is a single @@ -238,28 +248,6 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc return this; } - /** - * Sets a {@link VideoRendererEventListener} to be registered. - * - * @param eventListener A {@link VideoRendererEventListener} to be registered. - * @return This builder. - */ - public Builder setVideoRendererEventListener(VideoRendererEventListener eventListener) { - this.videoRendererEventListener = eventListener; - return this; - } - - /** - * Sets an {@link AudioRendererEventListener} to be registered. - * - * @param eventListener An {@link AudioRendererEventListener} to be registered. - * @return This builder. - */ - public Builder setAudioRendererEventListener(AudioRendererEventListener eventListener) { - this.audioRendererEventListener = eventListener; - return this; - } - /** * Sets an {@link AnalyticsListener} to be registered. * @@ -298,6 +286,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc if (trackSelector == null) { trackSelector = new DefaultTrackSelector(); } + if (bandwidthMeter == null) { + bandwidthMeter = new DefaultBandwidthMeter.Builder().build(); + } if (renderersFactory == null) { if (renderers == null) { renderers = new Renderer[] {new FakeRenderer(supportedFormats)}; @@ -332,10 +323,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc renderersFactory, trackSelector, loadControl, + bandwidthMeter, actionSchedule, eventListener, - videoRendererEventListener, - audioRendererEventListener, analyticsListener, expectedPlayerEndedCount); } @@ -347,10 +337,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc private final RenderersFactory renderersFactory; private final DefaultTrackSelector trackSelector; private final LoadControl loadControl; + private final BandwidthMeter bandwidthMeter; private final @Nullable ActionSchedule actionSchedule; private final @Nullable Player.EventListener eventListener; - private final @Nullable VideoRendererEventListener videoRendererEventListener; - private final @Nullable AudioRendererEventListener audioRendererEventListener; private final @Nullable AnalyticsListener analyticsListener; private final HandlerThread playerThread; @@ -375,10 +364,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc RenderersFactory renderersFactory, DefaultTrackSelector trackSelector, LoadControl loadControl, + BandwidthMeter bandwidthMeter, @Nullable ActionSchedule actionSchedule, @Nullable Player.EventListener eventListener, - @Nullable VideoRendererEventListener videoRendererEventListener, - @Nullable AudioRendererEventListener audioRendererEventListener, @Nullable AnalyticsListener analyticsListener, int expectedPlayerEndedCount) { this.context = context; @@ -387,10 +375,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc this.renderersFactory = renderersFactory; this.trackSelector = trackSelector; this.loadControl = loadControl; + this.bandwidthMeter = bandwidthMeter; this.actionSchedule = actionSchedule; this.eventListener = eventListener; - this.videoRendererEventListener = videoRendererEventListener; - this.audioRendererEventListener = audioRendererEventListener; this.analyticsListener = analyticsListener; this.timelines = new ArrayList<>(); this.manifests = new ArrayList<>(); @@ -419,17 +406,11 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc try { player = new TestSimpleExoPlayer( - context, renderersFactory, trackSelector, loadControl, clock); + context, renderersFactory, trackSelector, loadControl, bandwidthMeter, clock); player.addListener(ExoPlayerTestRunner.this); if (eventListener != null) { player.addListener(eventListener); } - if (videoRendererEventListener != null) { - player.addVideoDebugListener(videoRendererEventListener); - } - if (audioRendererEventListener != null) { - player.addAudioDebugListener(audioRendererEventListener); - } if (analyticsListener != null) { player.addAnalyticsListener(analyticsListener); } @@ -648,6 +629,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc RenderersFactory renderersFactory, TrackSelector trackSelector, LoadControl loadControl, + BandwidthMeter bandwidthMeter, Clock clock) { super( context, @@ -655,7 +637,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc trackSelector, loadControl, /* drmSessionManager= */ null, - new DefaultBandwidthMeter.Builder().build(), + bandwidthMeter, new AnalyticsCollector.Factory(), clock, Looper.myLooper()); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java index e6e4ea53cc..089528bfde 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java @@ -15,12 +15,10 @@ */ package com.google.android.exoplayer2.testutil; -import android.os.Handler; import android.support.annotation.Nullable; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.upstream.Allocator; @@ -38,12 +36,9 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { Timeline timeline, Object manifest, TrackGroupArray trackGroupArray, - Handler eventHandler, - MediaSourceEventListener eventListener, FakeChunkSource.Factory chunkSourceFactory) { super(timeline, manifest, trackGroupArray); this.chunkSourceFactory = chunkSourceFactory; - addEventListener(eventHandler, eventListener); } @Override diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java index d222b4f22f..9f6fdc9d49 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.testutil; import android.net.Uri; -import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData; import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; @@ -24,7 +23,6 @@ import com.google.android.exoplayer2.upstream.BaseDataSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; @@ -40,14 +38,9 @@ public class FakeDataSource extends BaseDataSource { */ public static class Factory implements DataSource.Factory { - protected final TransferListener transferListener; protected FakeDataSet fakeDataSet; protected boolean isNetwork; - public Factory(@Nullable TransferListener transferListener) { - this.transferListener = transferListener; - } - public final Factory setFakeDataSet(FakeDataSet fakeDataSet) { this.fakeDataSet = fakeDataSet; return this; @@ -60,11 +53,7 @@ public class FakeDataSource extends BaseDataSource { @Override public DataSource createDataSource() { - FakeDataSource dataSource = new FakeDataSource(fakeDataSet, isNetwork); - if (transferListener != null) { - dataSource.addTransferListener(transferListener); - } - return dataSource; + return new FakeDataSource(fakeDataSet, isNetwork); } } From 335fa130635b463e9b5d8acce30178fd6d180d9a Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 24 Aug 2018 03:43:50 -0700 Subject: [PATCH 32/42] Use set-like behaviour for BaseDataSource listeners. This prevents problems caused by unintended double-registration of the same transfer listener. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210078454 --- .../google/android/exoplayer2/upstream/BaseDataSource.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java index 18a7dcea49..5ed2e33d2b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BaseDataSource.java @@ -47,8 +47,10 @@ public abstract class BaseDataSource implements DataSource { @Override public final void addTransferListener(TransferListener transferListener) { - listeners.add(transferListener); - listenerCount++; + if (!listeners.contains(transferListener)) { + listeners.add(transferListener); + listenerCount++; + } } /** From 0da7f6ca08d88d07ab5600cb3d38b85058329a57 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 04:44:53 -0700 Subject: [PATCH 33/42] Use EventLogger in ExoHostedTest ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210082909 --- .../android/exoplayer2/util/EventLogger.java | 19 +++- .../exoplayer2/testutil/ExoHostedTest.java | 102 +++--------------- 2 files changed, 32 insertions(+), 89 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 0324630f1f..f9bd139298 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -44,7 +44,7 @@ import java.util.Locale; /** Logs events from {@link Player} and other core components using {@link Log}. */ public class EventLogger implements AnalyticsListener { - private static final String TAG = "EventLogger"; + private static final String DEFAULT_TAG = "EventLogger"; private static final int MAX_TIMELINE_ITEM_LINES = 3; private static final NumberFormat TIME_FORMAT; static { @@ -55,6 +55,7 @@ public class EventLogger implements AnalyticsListener { } private final @Nullable MappingTrackSelector trackSelector; + private final String tag; private final Timeline.Window window; private final Timeline.Period period; private final long startTimeMs; @@ -66,7 +67,19 @@ public class EventLogger implements AnalyticsListener { * logging of track mapping is not required. */ public EventLogger(@Nullable MappingTrackSelector trackSelector) { + this(trackSelector, DEFAULT_TAG); + } + + /** + * Creates event logger. + * + * @param trackSelector The mapping track selector used by the player. May be null if detailed + * logging of track mapping is not required. + * @param tag The tag used for logging. + */ + public EventLogger(@Nullable MappingTrackSelector trackSelector, String tag) { this.trackSelector = trackSelector; + this.tag = tag; window = new Timeline.Window(); period = new Timeline.Period(); startTimeMs = SystemClock.elapsedRealtime(); @@ -403,7 +416,7 @@ public class EventLogger implements AnalyticsListener { * @param msg The message to log. */ protected void logd(String msg) { - Log.d(TAG, msg); + Log.d(tag, msg); } /** @@ -413,7 +426,7 @@ public class EventLogger implements AnalyticsListener { * @param tr The exception to log. */ protected void loge(String msg, Throwable tr) { - Log.e(TAG, msg, tr); + Log.e(tag, msg, tr); } // Internal methods diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java index 3c6478c10a..efee52a472 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java @@ -22,17 +22,16 @@ import android.os.Looper; import android.os.SystemClock; import android.util.Log; import android.view.Surface; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.analytics.AnalyticsListener; -import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DrmSessionManager; @@ -43,16 +42,12 @@ import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoRendererEventListener; /** A {@link HostedTest} for {@link ExoPlayer} playback tests. */ -public abstract class ExoHostedTest - implements Player.EventListener, - HostedTest, - AudioRendererEventListener, - VideoRendererEventListener { +public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { static { // DefaultAudioSink is able to work around spurious timestamps reported by the platform (by @@ -151,12 +146,11 @@ public abstract class ExoHostedTest DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); player = buildExoPlayer(host, surface, trackSelector, drmSessionManager); player.prepare(buildSource(host, Util.getUserAgent(host, userAgent))); + player.addAnalyticsListener(this); + player.addAnalyticsListener(new EventLogger(trackSelector, tag)); if (analyticsListener != null) { player.addAnalyticsListener(analyticsListener); } - player.addListener(this); - player.addAudioDebugListener(this); - player.addVideoDebugListener(this); player.setPlayWhenReady(true); actionHandler = Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null); // Schedule any pending actions. @@ -199,10 +193,11 @@ public abstract class ExoHostedTest assertPassed(audioDecoderCounters, videoDecoderCounters); } - // Player.EventListener + // AnalyticsListener @Override - public final void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + public final void onPlayerStateChanged( + EventTime eventTime, boolean playWhenReady, int playbackState) { Log.d(tag, "state [" + playWhenReady + ", " + playbackState + "]"); playerWasPrepared |= playbackState != Player.STATE_IDLE; if (playbackState == Player.STATE_ENDED @@ -219,85 +214,20 @@ public abstract class ExoHostedTest } @Override - public final void onPlayerError(ExoPlaybackException error) { + public final void onPlayerError(EventTime eventTime, ExoPlaybackException error) { playerWasPrepared = true; playerError = error; onPlayerErrorInternal(error); } - // AudioRendererEventListener - @Override - public void onAudioEnabled(DecoderCounters counters) { - Log.d(tag, "audioEnabled"); - } - - @Override - public void onAudioSessionId(int audioSessionId) { - Log.d(tag, "audioSessionId [" + audioSessionId + "]"); - } - - @Override - public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs, - long initializationDurationMs) { - Log.d(tag, "audioDecoderInitialized [" + decoderName + "]"); - } - - @Override - public void onAudioInputFormatChanged(Format format) { - Log.d(tag, "audioFormatChanged [" + Format.toLogString(format) + "]"); - } - - @Override - public void onAudioDisabled(DecoderCounters counters) { - Log.d(tag, "audioDisabled"); - audioDecoderCounters.merge(counters); - } - - @Override - public void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { - Log.e(tag, "audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", " - + elapsedSinceLastFeedMs + "]", null); - } - - // VideoRendererEventListener - - @Override - public void onVideoEnabled(DecoderCounters counters) { - Log.d(tag, "videoEnabled"); - } - - @Override - public void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs, - long initializationDurationMs) { - Log.d(tag, "videoDecoderInitialized [" + decoderName + "]"); - } - - @Override - public void onVideoInputFormatChanged(Format format) { - Log.d(tag, "videoFormatChanged [" + Format.toLogString(format) + "]"); - } - - @Override - public void onVideoDisabled(DecoderCounters counters) { - Log.d(tag, "videoDisabled"); - videoDecoderCounters.merge(counters); - } - - @Override - public void onDroppedFrames(int count, long elapsed) { - Log.d(tag, "droppedFrames [" + count + "]"); - } - - @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio) { - // Do nothing. - } - - @Override - public void onRenderedFirstFrame(Surface surface) { - // Do nothing. + public void onDecoderDisabled( + EventTime eventTime, int trackType, DecoderCounters decoderCounters) { + if (trackType == C.TRACK_TYPE_AUDIO) { + audioDecoderCounters.merge(decoderCounters); + } else if (trackType == C.TRACK_TYPE_VIDEO) { + videoDecoderCounters.merge(decoderCounters); + } } // Internal logic From 924a76d5323e072eb075bf0a6e50fd5eda7fd064 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 06:51:33 -0700 Subject: [PATCH 34/42] Clean up use of deprecated APIs - Add @Deprecated on overrides of deprecated method. - Suppress deprecation warnings where appropriate. - Use non-deprecated alternatives where appropriate. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210092434 --- .../android/exoplayer2/imademo/PlayerManager.java | 4 +--- .../exoplayer2/demo/SampleChooserActivity.java | 3 ++- .../exoplayer2/ext/rtmp/RtmpDataSourceFactory.java | 6 +++++- .../android/exoplayer2/DefaultLoadControl.java | 8 +++++--- .../exoplayer2/DefaultRenderersFactory.java | 11 ++++++++--- .../com/google/android/exoplayer2/ExoPlayer.java | 2 ++ .../google/android/exoplayer2/ExoPlayerImpl.java | 4 ++++ .../java/com/google/android/exoplayer2/Player.java | 1 + .../google/android/exoplayer2/SimpleExoPlayer.java | 8 ++++++++ .../exoplayer2/source/ExtractorMediaSource.java | 5 +++++ .../exoplayer2/source/SingleSampleMediaSource.java | 4 ++++ .../trackselection/DefaultTrackSelector.java | 1 + .../trackselection/MappingTrackSelector.java | 1 + .../android/exoplayer2/upstream/DataSpec.java | 1 + .../exoplayer2/upstream/DefaultDataSource.java | 2 ++ .../upstream/DefaultDataSourceFactory.java | 7 ++++++- .../exoplayer2/upstream/DefaultHttpDataSource.java | 2 ++ .../upstream/DefaultHttpDataSourceFactory.java | 14 ++++++++++++-- .../exoplayer2/upstream/FileDataSourceFactory.java | 6 +++++- .../android/exoplayer2/upstream/UdpDataSource.java | 2 ++ .../android/exoplayer2/drm/DrmInitDataTest.java | 3 ++- .../trackselection/AdaptiveTrackSelectionTest.java | 1 + .../exoplayer2/source/hls/HlsMediaSource.java | 3 +++ .../source/smoothstreaming/SsMediaSource.java | 3 +++ .../android/exoplayer2/ui/PlaybackControlView.java | 3 +++ .../android/exoplayer2/ui/SimpleExoPlayerView.java | 4 ++++ .../testutil/FakeAdaptiveMediaPeriod.java | 11 ++++++----- .../exoplayer2/testutil/FakeTrackSelector.java | 5 +++-- .../android/exoplayer2/testutil/StubExoPlayer.java | 4 ++++ 29 files changed, 106 insertions(+), 23 deletions(-) diff --git a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java index 97e618ba52..d67c4549d8 100644 --- a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java +++ b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java @@ -76,9 +76,7 @@ import com.google.android.exoplayer2.util.Util; contentMediaSource, /* adMediaSourceFactory= */ this, adsLoader, - playerView.getOverlayFrameLayout(), - /* eventHandler= */ null, - /* eventListener= */ null); + playerView.getOverlayFrameLayout()); // Prepare the player with the source. player.seekTo(contentPosition); 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 7dc7890020..6817fab780 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 @@ -213,7 +213,8 @@ public class SampleChooserActivity extends Activity List result = new ArrayList<>(); Context context = getApplicationContext(); String userAgent = Util.getUserAgent(context, "ExoPlayerDemo"); - DataSource dataSource = new DefaultDataSource(context, null, userAgent, false); + DataSource dataSource = + new DefaultDataSource(context, userAgent, /* allowCrossProtocolRedirects= */ false); for (String uri : uris) { DataSpec dataSpec = new DataSpec(Uri.parse(uri)); InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); diff --git a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java index 3cf9b8de37..d1350276f2 100644 --- a/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java +++ b/extensions/rtmp/src/main/java/com/google/android/exoplayer2/ext/rtmp/RtmpDataSourceFactory.java @@ -38,7 +38,11 @@ public final class RtmpDataSourceFactory implements DataSource.Factory { @Override public DataSource createDataSource() { - return new RtmpDataSource(listener); + RtmpDataSource dataSource = new RtmpDataSource(); + if (listener != null) { + dataSource.addTransferListener(listener); + } + return dataSource; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index f8b7f5f5c2..c466815c79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -154,6 +154,7 @@ public class DefaultLoadControl implements LoadControl { } /** Creates a {@link DefaultLoadControl}. */ + @SuppressWarnings("deprecation") public DefaultLoadControl createDefaultLoadControl() { if (allocator == null) { allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); @@ -183,15 +184,15 @@ public class DefaultLoadControl implements LoadControl { private int targetBufferSize; private boolean isBuffering; - /** - * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. - */ + /** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */ + @SuppressWarnings("deprecation") public DefaultLoadControl() { this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); } /** @deprecated Use {@link Builder} instead. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultLoadControl(DefaultAllocator allocator) { this( allocator, @@ -205,6 +206,7 @@ public class DefaultLoadControl implements LoadControl { /** @deprecated Use {@link Builder} instead. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultLoadControl( DefaultAllocator allocator, int minBufferMs, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index f625519b8a..ff7e953896 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -82,7 +82,7 @@ public class DefaultRenderersFactory implements RenderersFactory { protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; private final Context context; - @Nullable private final DrmSessionManager drmSessionManager; + private final @Nullable DrmSessionManager drmSessionManager; private final @ExtensionRendererMode int extensionRendererMode; private final long allowedVideoJoiningTimeMs; @@ -98,6 +98,7 @@ public class DefaultRenderersFactory implements RenderersFactory { * directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultRenderersFactory( Context context, @Nullable DrmSessionManager drmSessionManager) { this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF); @@ -111,7 +112,7 @@ public class DefaultRenderersFactory implements RenderersFactory { */ public DefaultRenderersFactory( Context context, @ExtensionRendererMode int extensionRendererMode) { - this(context, null, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + this(context, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); } /** @@ -119,6 +120,7 @@ public class DefaultRenderersFactory implements RenderersFactory { * DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultRenderersFactory( Context context, @Nullable DrmSessionManager drmSessionManager, @@ -138,7 +140,10 @@ public class DefaultRenderersFactory implements RenderersFactory { Context context, @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { - this(context, null, extensionRendererMode, allowedVideoJoiningTimeMs); + this.context = context; + this.extensionRendererMode = extensionRendererMode; + this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; + this.drmSessionManager = null; } /** 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 5780f7b418..452c1043a3 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 @@ -227,6 +227,7 @@ public interface ExoPlayer extends Player { /** @deprecated Use {@link #createMessage(PlayerMessage.Target)} instead. */ @Deprecated + @SuppressWarnings("deprecation") void sendMessages(ExoPlayerMessage... messages); /** @@ -234,6 +235,7 @@ public interface ExoPlayer extends Player { * PlayerMessage#blockUntilDelivered()}. */ @Deprecated + @SuppressWarnings("deprecation") void blockingSendMessages(ExoPlayerMessage... messages); /** 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 b663b1185c..5e0dd905b9 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 @@ -415,6 +415,8 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override + @Deprecated + @SuppressWarnings("deprecation") public void sendMessages(ExoPlayerMessage... messages) { for (ExoPlayerMessage message : messages) { createMessage(message.target).setType(message.messageType).setPayload(message.message).send(); @@ -432,6 +434,8 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override + @Deprecated + @SuppressWarnings("deprecation") public void blockingSendMessages(ExoPlayerMessage... messages) { List playerMessages = new ArrayList<>(); for (ExoPlayerMessage message : messages) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 0e4cf68aa4..32722ae41e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -392,6 +392,7 @@ public interface Player { abstract class DefaultEventListener implements EventListener { @Override + @SuppressWarnings("deprecation") public void onTimelineChanged( Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { // Call deprecated version. Otherwise, do nothing. 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 de150ddfe3..a86a8d4d79 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 @@ -604,6 +604,7 @@ public class SimpleExoPlayer * @deprecated Use {@link #addVideoListener(com.google.android.exoplayer2.video.VideoListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public void setVideoListener(VideoListener listener) { videoListeners.clear(); if (listener != null) { @@ -619,6 +620,7 @@ public class SimpleExoPlayer * #removeVideoListener(com.google.android.exoplayer2.video.VideoListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public void clearVideoListener(VideoListener listener) { removeVideoListener(listener); } @@ -709,6 +711,7 @@ public class SimpleExoPlayer * information. */ @Deprecated + @SuppressWarnings("deprecation") public void setVideoDebugListener(VideoRendererEventListener listener) { videoDebugListeners.retainAll(Collections.singleton(analyticsCollector)); if (listener != null) { @@ -739,6 +742,7 @@ public class SimpleExoPlayer * information. */ @Deprecated + @SuppressWarnings("deprecation") public void setAudioDebugListener(AudioRendererEventListener listener) { audioDebugListeners.retainAll(Collections.singleton(analyticsCollector)); if (listener != null) { @@ -939,6 +943,8 @@ public class SimpleExoPlayer } @Override + @Deprecated + @SuppressWarnings("deprecation") public void sendMessages(ExoPlayerMessage... messages) { player.sendMessages(messages); } @@ -949,6 +955,8 @@ public class SimpleExoPlayer } @Override + @Deprecated + @SuppressWarnings("deprecation") public void blockingSendMessages(ExoPlayerMessage... messages) { player.blockingSendMessages(messages); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 2c0a98f431..66af3a7e62 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -262,6 +262,7 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -284,6 +285,7 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -316,6 +318,7 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -426,6 +429,8 @@ public final class ExtractorMediaSource extends BaseMediaSource * Wraps a deprecated {@link EventListener}, invoking its callback from the equivalent callback in * {@link MediaSourceEventListener}. */ + @Deprecated + @SuppressWarnings("deprecation") private static final class EventListenerWrapper extends DefaultMediaSourceEventListener { private final EventListener eventListener; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index c75c9541a4..dc46b12b2e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -197,6 +197,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public SingleSampleMediaSource( Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs) { this( @@ -249,6 +250,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public SingleSampleMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -333,6 +335,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource { * Wraps a deprecated {@link EventListener}, invoking its callback from the equivalent callback in * {@link MediaSourceEventListener}. */ + @Deprecated + @SuppressWarnings("deprecation") private static final class EventListenerWrapper extends DefaultMediaSourceEventListener { private final EventListener eventListener; 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 fbdbb8d95f..5e4aef958c 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 @@ -1095,6 +1095,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { * directly passed to the player in ExoPlayerFactory. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultTrackSelector(BandwidthMeter bandwidthMeter) { this(new AdaptiveTrackSelection.Factory(bandwidthMeter)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index a243b1a813..c2fda67728 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -99,6 +99,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * each mapped track, indexed by renderer, track group and track (in that order). * @param unmappedTrackGroups {@link TrackGroup}s not mapped to any renderer. */ + @SuppressWarnings("deprecation") /* package */ MappedTrackInfo( int[] rendererTrackTypes, TrackGroupArray[] rendererTrackGroups, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index 653673ed97..c968921822 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -226,6 +226,7 @@ public final class DataSpec { * @param key {@link #key}. * @param flags {@link #flags}. */ + @SuppressWarnings("deprecation") public DataSpec( Uri uri, @HttpMethod int httpMethod, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java index 23d6cc368f..411240d56c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java @@ -142,6 +142,7 @@ public final class DefaultDataSource implements DataSource { * #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultDataSource( Context context, @Nullable TransferListener listener, @@ -167,6 +168,7 @@ public final class DefaultDataSource implements DataSource { * #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultDataSource( Context context, @Nullable TransferListener listener, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java index 293ba7f17b..8183a89064 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java @@ -75,6 +75,11 @@ public final class DefaultDataSourceFactory implements Factory { @Override public DefaultDataSource createDataSource() { - return new DefaultDataSource(context, listener, baseDataSourceFactory.createDataSource()); + DefaultDataSource dataSource = + new DefaultDataSource(context, baseDataSourceFactory.createDataSource()); + if (listener != null) { + dataSource.addTransferListener(listener); + } + return dataSource; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 06e3dc7e79..f02f1cc7c4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -168,6 +168,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou * #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultHttpDataSource( String userAgent, @Nullable Predicate contentTypePredicate, @@ -190,6 +191,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou * #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public DefaultHttpDataSource( String userAgent, @Nullable Predicate contentTypePredicate, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java index aa0ac7b97e..95ea49132d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java @@ -103,7 +103,17 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory { @Override protected DefaultHttpDataSource createDataSourceInternal( HttpDataSource.RequestProperties defaultRequestProperties) { - return new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, - readTimeoutMillis, allowCrossProtocolRedirects, defaultRequestProperties); + DefaultHttpDataSource dataSource = + new DefaultHttpDataSource( + userAgent, + /* contentTypePredicate= */ null, + connectTimeoutMillis, + readTimeoutMillis, + allowCrossProtocolRedirects, + defaultRequestProperties); + if (listener != null) { + dataSource.addTransferListener(listener); + } + return dataSource; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java index f69adeb8c2..fd1920991e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java @@ -34,7 +34,11 @@ public final class FileDataSourceFactory implements DataSource.Factory { @Override public DataSource createDataSource() { - return new FileDataSource(listener); + FileDataSource dataSource = new FileDataSource(); + if (listener != null) { + dataSource.addTransferListener(listener); + } + return dataSource; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java index 47677d2c47..8d6b39fa98 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java @@ -95,6 +95,7 @@ public final class UdpDataSource extends BaseDataSource { * @deprecated Use {@link #UdpDataSource()} and {@link #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public UdpDataSource(@Nullable TransferListener listener) { this(listener, DEFAULT_MAX_PACKET_SIZE); } @@ -107,6 +108,7 @@ public final class UdpDataSource extends BaseDataSource { * @deprecated Use {@link #UdpDataSource(int)} and {@link #addTransferListener(TransferListener)}. */ @Deprecated + @SuppressWarnings("deprecation") public UdpDataSource(@Nullable TransferListener listener, int maxPacketSize) { this(listener, maxPacketSize, DEFAULT_SOCKET_TIMEOUT_MILLIS); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java index 2b3bdd6a2f..2b740de113 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java @@ -96,7 +96,7 @@ public class DrmInitDataTest { } @Test - @Deprecated + @SuppressWarnings("deprecation") public void testGetByUuid() { // Basic matching. DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2); @@ -130,6 +130,7 @@ public class DrmInitDataTest { } @Test + @SuppressWarnings("deprecation") public void testSchemeDatasWithSameUuid() { DrmInitData testInitData = new DrmInitData(DATA_1, DATA_1B); assertThat(testInitData.schemeDataCount).isEqualTo(2); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java index 730572bbd8..348352eb30 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java @@ -63,6 +63,7 @@ public final class AdaptiveTrackSelectionTest { BandwidthMeter initialBandwidthMeter = mock(BandwidthMeter.class); BandwidthMeter injectedBandwidthMeter = mock(BandwidthMeter.class); Format format = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); + @SuppressWarnings("deprecation") AdaptiveTrackSelection adaptiveTrackSelection = new AdaptiveTrackSelection.Factory(initialBandwidthMeter) .createTrackSelection(new TrackGroup(format), injectedBandwidthMeter, /* tracks= */ 0); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 24067fcab1..79b030a0ee 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -309,6 +309,7 @@ public final class HlsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public HlsMediaSource( Uri manifestUri, DataSource.Factory dataSourceFactory, @@ -334,6 +335,7 @@ public final class HlsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public HlsMediaSource( Uri manifestUri, DataSource.Factory dataSourceFactory, @@ -364,6 +366,7 @@ public final class HlsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public HlsMediaSource( Uri manifestUri, HlsDataSourceFactory dataSourceFactory, diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 83f1a59c8b..a756b7f4f1 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -330,6 +330,7 @@ public final class SsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public SsMediaSource( SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, @@ -388,6 +389,7 @@ public final class SsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public SsMediaSource( Uri manifestUri, DataSource.Factory manifestDataSourceFactory, @@ -420,6 +422,7 @@ public final class SsMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated + @SuppressWarnings("deprecation") public SsMediaSource( Uri manifestUri, DataSource.Factory manifestDataSourceFactory, diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index da03d28cba..5467538c0f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -34,10 +34,13 @@ public class PlaybackControlView extends PlayerControlView { public interface VisibilityListener extends com.google.android.exoplayer2.ui.PlayerControlView.VisibilityListener {} + @Deprecated + @SuppressWarnings("deprecation") private static final class DefaultControlDispatcher extends com.google.android.exoplayer2.DefaultControlDispatcher implements ControlDispatcher {} /** @deprecated Use {@link com.google.android.exoplayer2.DefaultControlDispatcher}. */ @Deprecated + @SuppressWarnings("deprecation") public static final ControlDispatcher DEFAULT_CONTROL_DISPATCHER = new DefaultControlDispatcher(); /** The default fast forward increment, in milliseconds. */ diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index b8098b6fa7..55745a7cb5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; +import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; /** @deprecated Use {@link PlayerView}. */ @@ -45,7 +46,10 @@ public final class SimpleExoPlayerView extends PlayerView { * @param player The player whose target view is being switched. * @param oldPlayerView The old view to detach from the player. * @param newPlayerView The new view to attach to the player. + * @deprecated Use {@link PlayerView#switchTargetView(Player, PlayerView, PlayerView)} instead. */ + @Deprecated + @SuppressWarnings("deprecation") public static void switchTargetView( @NonNull SimpleExoPlayer player, @Nullable SimpleExoPlayerView oldPlayerView, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index 70c40b76e9..f8bf950ef2 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; import java.util.ArrayList; @@ -142,13 +143,13 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod chunkSourceFactory.createChunkSource(trackSelection, durationUs, transferListener); return new ChunkSampleStream<>( MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType), - null, - null, + /* embeddedTrackTypes= */ null, + /* embeddedTrackFormats= */ null, chunkSource, - this, + /* callback= */ this, allocator, - 0, - 3, + /* positionUs= */ 0, + new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3), eventDispatcher); } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java index 4d4a53bcdd..d3eca63461 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java @@ -51,8 +51,9 @@ public class FakeTrackSelector extends DefaultTrackSelector { int[] rendererMixedMimeTypeAdaptationSupports, Parameters params) throws ExoPlaybackException { - TrackSelection[] selections = new TrackSelection[mappedTrackInfo.length]; - for (int i = 0; i < mappedTrackInfo.length; i++) { + int rendererCount = mappedTrackInfo.getRendererCount(); + TrackSelection[] selections = new TrackSelection[rendererCount]; + for (int i = 0; i < rendererCount; i++) { TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); boolean hasTracks = trackGroupArray.length > 0; selections[i] = hasTracks ? reuseOrCreateTrackSelection(trackGroupArray.get(0)) : null; diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 6c99da55cc..be41aa3eeb 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -190,11 +190,15 @@ public abstract class StubExoPlayer implements ExoPlayer { } @Override + @Deprecated + @SuppressWarnings("deprecation") public void sendMessages(ExoPlayerMessage... messages) { throw new UnsupportedOperationException(); } @Override + @Deprecated + @SuppressWarnings("deprecation") public void blockingSendMessages(ExoPlayerMessage... messages) { throw new UnsupportedOperationException(); } From 3196bc40dbac54bac272cedc4fe42f47f551b88b Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 06:53:31 -0700 Subject: [PATCH 35/42] Fix CronetDataSource redirect logic - Remove usage of deprecated postBody field - Transform POST to GET on redirect, as in DefaultHttpDataSource ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210092576 --- .../ext/cronet/CronetDataSource.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index fd6a3ce9ec..5c6f5dafd9 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -606,11 +606,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (request != currentUrlRequest) { return; } - if (currentDataSpec.postBody != null) { + if (currentDataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) { int responseCode = info.getHttpStatusCode(); // The industry standard is to disregard POST redirects when the status code is 307 or 308. - // For other redirect response codes the POST request is converted to a GET request and the - // redirect is followed. if (responseCode == 307 || responseCode == 308) { exception = new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec); @@ -627,7 +625,23 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { request.followRedirect(); } else { currentUrlRequest.cancel(); - DataSpec redirectUrlDataSpec = currentDataSpec.withUri(Uri.parse(newLocationUrl)); + DataSpec redirectUrlDataSpec; + if (currentDataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) { + // For POST redirects that aren't 307 or 308, the redirect is followed but request is + // transformed into a GET. + redirectUrlDataSpec = + new DataSpec( + Uri.parse(newLocationUrl), + DataSpec.HTTP_METHOD_GET, + /* httpBody= */ null, + currentDataSpec.absoluteStreamPosition, + currentDataSpec.position, + currentDataSpec.length, + currentDataSpec.key, + currentDataSpec.flags); + } else { + redirectUrlDataSpec = currentDataSpec.withUri(Uri.parse(newLocationUrl)); + } UrlRequest.Builder requestBuilder; try { requestBuilder = buildRequestBuilder(redirectUrlDataSpec); From fdda2bb8414a2ae7ee832a1c97c64c9958a0edeb Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 07:00:47 -0700 Subject: [PATCH 36/42] Make setConstantBitrateSeekingEnabled consistent with other method ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210093143 --- .../exoplayer2/extractor/DefaultExtractorsFactory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index f2c3d982d2..54bb617c58 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -95,9 +95,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { * * @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate * assumption should be enabled for all extractors that support it. + * @return The factory, for convenience. */ - public void setConstantBitrateSeekingEnabled(boolean constantBitrateSeekingEnabled) { + public synchronized DefaultExtractorsFactory setConstantBitrateSeekingEnabled( + boolean constantBitrateSeekingEnabled) { this.constantBitrateSeekingEnabled = constantBitrateSeekingEnabled; + return this; } /** From ff1812d3fed7d18baefb778b91f7397c9c61a8bc Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 24 Aug 2018 07:11:19 -0700 Subject: [PATCH 37/42] Fix ClearKey prior to API 27 There are C.CLEARKEY_UUID.equals(uuid) checks in FrameworkMediaDrm, so uuid needs to be CLEARKEY_UUID, not COMMON_PSSH_UUID ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210094372 --- .../google/android/exoplayer2/drm/FrameworkMediaDrm.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 548f9cb669..255541971e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -66,10 +66,10 @@ public final class FrameworkMediaDrm implements ExoMediaDrm Date: Fri, 24 Aug 2018 09:14:51 -0700 Subject: [PATCH 38/42] Fix all error-prone, style, optional suggestion, etc. issues. This fixes or suppresses all reported issues by the code review linter tools. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210107759 --- .../ext/cronet/CronetEngineWrapper.java | 7 ++- .../ByteArrayUploadDataProviderTest.java | 2 +- .../ext/cronet/CronetDataSourceTest.java | 28 +++++++----- .../exoplayer2/ext/flac/FlacPlaybackTest.java | 10 ++--- .../exoplayer2/ext/flac/FlacDecoderJni.java | 1 + .../flac/DefaultExtractorsFactoryTest.java | 6 +-- .../exoplayer2/ext/ima/FakePlayer.java | 2 +- .../jobdispatcher/JobDispatcherScheduler.java | 3 +- .../ext/okhttp/OkHttpDataSource.java | 4 +- .../exoplayer2/ext/opus/OpusPlaybackTest.java | 10 ++--- .../exoplayer2/ext/vp9/VpxPlaybackTest.java | 14 +++--- .../upstream/ContentDataSourceTest.java | 2 +- .../java/com/google/android/exoplayer2/C.java | 20 ++++----- .../com/google/android/exoplayer2/Player.java | 16 +++---- .../android/exoplayer2/PlayerMessage.java | 16 +++---- .../android/exoplayer2/SimpleExoPlayer.java | 14 +++--- .../analytics/AnalyticsCollector.java | 10 ++--- .../decoder/DecoderInputBuffer.java | 6 +-- .../exoplayer2/decoder/SimpleDecoder.java | 10 ++--- .../exoplayer2/extractor/MpegAudioHeader.java | 4 +- .../exoplayer2/extractor/mp4/Atom.java | 1 + .../exoplayer2/extractor/mp4/AtomParsers.java | 13 +++--- .../extractor/mp4/FragmentedMp4Extractor.java | 3 ++ .../extractor/ogg/StreamReader.java | 5 +-- .../extractor/ogg/VorbisReader.java | 2 +- .../exoplayer2/extractor/ts/AdtsReader.java | 2 + .../exoplayer2/extractor/ts/DtsReader.java | 2 + .../exoplayer2/extractor/ts/LatmReader.java | 4 ++ .../extractor/ts/MpegAudioReader.java | 2 + .../exoplayer2/extractor/ts/PesReader.java | 4 ++ .../exoplayer2/mediacodec/MediaCodecUtil.java | 45 ++++++++++++------- .../metadata/emsg/EventMessageDecoder.java | 1 + .../exoplayer2/metadata/id3/Id3Decoder.java | 4 +- .../metadata/scte35/SpliceInfoDecoder.java | 1 + .../exoplayer2/offline/DownloadAction.java | 1 + .../exoplayer2/offline/SegmentDownloader.java | 4 +- .../text/SimpleSubtitleDecoder.java | 5 ++- .../exoplayer2/text/cea/Cea608Decoder.java | 1 + .../trackselection/BaseTrackSelection.java | 2 +- .../upstream/ContentDataSource.java | 1 + .../upstream/RawResourceDataSource.java | 1 + .../exoplayer2/ui/SubtitlePainter.java | 1 + 42 files changed, 160 insertions(+), 130 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index 1e24e3eb7c..9257411e3c 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cronet; import android.content.Context; import android.support.annotation.IntDef; import android.util.Log; +import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; @@ -159,6 +160,8 @@ public final class CronetEngineWrapper { private final String gmsCoreCronetName; private final boolean preferGMSCoreCronet; + // Multi-catch can only be used for API 19+ in this case. + @SuppressWarnings("UseMultiCatch") public CronetProviderComparator(boolean preferGMSCoreCronet) { // GMSCore CronetProvider classes are only available in some configurations. // Thus, we use reflection to copy static name. @@ -219,8 +222,8 @@ public final class CronetEngineWrapper { if (versionLeft == null || versionRight == null) { return 0; } - String[] versionStringsLeft = versionLeft.split("\\."); - String[] versionStringsRight = versionRight.split("\\."); + String[] versionStringsLeft = Util.split(versionLeft, "\\."); + String[] versionStringsRight = Util.split(versionRight, "\\."); int minLength = Math.min(versionStringsLeft.length, versionStringsRight.length); for (int i = 0; i < minLength; i++) { if (!versionStringsLeft[i].equals(versionStringsRight[i])) { diff --git a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java index 291e73fcc1..117518a1eb 100644 --- a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java @@ -60,7 +60,7 @@ public final class ByteArrayUploadDataProviderTest { @Test public void testReadPartialBuffer() throws IOException { - byte[] firstHalf = Arrays.copyOfRange(TEST_DATA, 0, TEST_DATA.length / 2); + byte[] firstHalf = Arrays.copyOf(TEST_DATA, TEST_DATA.length / 2); byte[] secondHalf = Arrays.copyOfRange(TEST_DATA, TEST_DATA.length / 2, TEST_DATA.length); byteBuffer = ByteBuffer.allocate(TEST_DATA.length / 2); // Read half of the data. diff --git a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index 4013caf09c..7d47b0da64 100644 --- a/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceExcep import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Predicate; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; @@ -50,6 +51,7 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.chromium.net.CronetEngine; import org.chromium.net.NetworkException; import org.chromium.net.UrlRequest; @@ -61,7 +63,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.shadows.ShadowSystemClock; /** Tests for {@link CronetDataSource}. */ @RunWith(RobolectricTestRunner.class) @@ -71,7 +72,7 @@ public final class CronetDataSourceTest { private static final int TEST_READ_TIMEOUT_MS = 100; private static final String TEST_URL = "http://google.com"; private static final String TEST_CONTENT_TYPE = "test/test"; - private static final byte[] TEST_POST_BODY = "test post body".getBytes(); + private static final byte[] TEST_POST_BODY = Util.getUtf8Bytes("test post body"); private static final long TEST_CONTENT_LENGTH = 16000L; private static final int TEST_CONNECTION_STATUS = 5; private static final int TEST_INVALID_CONNECTION_STATUS = -1; @@ -577,10 +578,10 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); // Now we timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); + SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); timedOutLatch.await(); verify(mockTransferListener, never()) @@ -616,7 +617,7 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); // Now we interrupt. thread.interrupt(); @@ -632,14 +633,16 @@ public final class CronetDataSourceTest { final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final CountDownLatch openLatch = new CountDownLatch(1); + AtomicReference exceptionOnTestThread = new AtomicReference<>(); new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); - openLatch.countDown(); } catch (HttpDataSourceException e) { - fail(); + exceptionOnTestThread.set(e); + } finally { + openLatch.countDown(); } } }.start(); @@ -648,11 +651,12 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertNotCountedDown(openLatch); // We should still be trying to open as we approach the timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(openLatch); // The response arrives just in time. dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo); openLatch.await(); + assertThat(exceptionOnTestThread.get()).isNull(); } @Test @@ -682,14 +686,14 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); // A redirect arrives just in time. dataSourceUnderTest.urlRequestCallback.onRedirectReceived( mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); // We should still be trying to open as we approach the new timeout. assertNotCountedDown(timedOutLatch); // A redirect arrives just in time. @@ -697,11 +701,11 @@ public final class CronetDataSourceTest { mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); + SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); // We should still be trying to open as we approach the new timeout. assertNotCountedDown(timedOutLatch); // Now we timeout. - ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10); + SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10); timedOutLatch.await(); verify(mockTransferListener, never()) diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index a78556b6c7..99ddba55c4 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -50,20 +50,16 @@ public class FlacPlaybackTest { } @Test - public void testBasicPlayback() throws ExoPlaybackException { + public void testBasicPlayback() throws Exception { playUri(BEAR_FLAC_URI); } - private void playUri(String uri) throws ExoPlaybackException { + private void playUri(String uri) throws Exception { TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - fail(); // Should never happen. - } + thread.join(); if (testPlaybackRunnable.playbackException != null) { throw testPlaybackRunnable.playbackException; } diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java index 69c0d082ee..de038921aa 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java @@ -155,6 +155,7 @@ import java.nio.ByteBuffer; } /** Decodes and consumes the next sample from the FLAC stream into the given byte buffer. */ + @SuppressWarnings("ByteBufferBackingArray") public void decodeSample(ByteBuffer output) throws IOException, InterruptedException, FlacFrameDecodeException { output.clear(); diff --git a/extensions/flac/src/test/java/com/google/android/exoplayer2/ext/flac/DefaultExtractorsFactoryTest.java b/extensions/flac/src/test/java/com/google/android/exoplayer2/ext/flac/DefaultExtractorsFactoryTest.java index e08f4dc28c..79c4452928 100644 --- a/extensions/flac/src/test/java/com/google/android/exoplayer2/ext/flac/DefaultExtractorsFactoryTest.java +++ b/extensions/flac/src/test/java/com/google/android/exoplayer2/ext/flac/DefaultExtractorsFactoryTest.java @@ -46,13 +46,13 @@ public final class DefaultExtractorsFactoryTest { DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); Extractor[] extractors = defaultExtractorsFactory.createExtractors(); - List listCreatedExtractorClasses = new ArrayList<>(); + List> listCreatedExtractorClasses = new ArrayList<>(); for (Extractor extractor : extractors) { listCreatedExtractorClasses.add(extractor.getClass()); } - Class[] expectedExtractorClassses = - new Class[] { + Class[] expectedExtractorClassses = + new Class[] { MatroskaExtractor.class, FragmentedMp4Extractor.class, Mp4Extractor.class, diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index 11ed214279..c7026bab5f 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -27,9 +27,9 @@ import java.util.ArrayList; private final ArrayList listeners; private final Timeline.Window window; private final Timeline.Period period; + private final Timeline timeline; private boolean prepared; - private Timeline timeline; private int state; private boolean playWhenReady; private long position; diff --git a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java index 5227411266..d6759245c0 100644 --- a/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java +++ b/extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java @@ -23,7 +23,6 @@ import com.firebase.jobdispatcher.Constraint; import com.firebase.jobdispatcher.FirebaseJobDispatcher; import com.firebase.jobdispatcher.GooglePlayDriver; import com.firebase.jobdispatcher.Job; -import com.firebase.jobdispatcher.Job.Builder; import com.firebase.jobdispatcher.JobParameters; import com.firebase.jobdispatcher.JobService; import com.firebase.jobdispatcher.Lifetime; @@ -99,7 +98,7 @@ public final class JobDispatcherScheduler implements Scheduler { String tag, String serviceAction, String servicePackage) { - Builder builder = + Job.Builder builder = dispatcher .newJobBuilder() .setService(JobDispatcherSchedulerService.class) // the JobService that will be called diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 1d0dfddb3f..ba5640c4e0 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -161,8 +161,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { responseBody = Assertions.checkNotNull(response.body()); responseByteStream = responseBody.byteStream(); } catch (IOException e) { - throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, - dataSpec, HttpDataSourceException.TYPE_OPEN); + throw new HttpDataSourceException( + "Unable to connect to " + dataSpec.uri, e, dataSpec, HttpDataSourceException.TYPE_OPEN); } int responseCode = response.code(); diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index cad63f84df..c457514c87 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -50,20 +50,16 @@ public class OpusPlaybackTest { } @Test - public void testBasicPlayback() throws ExoPlaybackException { + public void testBasicPlayback() throws Exception { playUri(BEAR_OPUS_URI); } - private void playUri(String uri) throws ExoPlaybackException { + private void playUri(String uri) throws Exception { TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - fail(); // Should never happen. - } + thread.join(); if (testPlaybackRunnable.playbackException != null) { throw testPlaybackRunnable.playbackException; } diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 119347ccbf..d06e2934fb 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -57,17 +57,17 @@ public class VpxPlaybackTest { } @Test - public void testBasicPlayback() throws ExoPlaybackException { + public void testBasicPlayback() throws Exception { playUri(BEAR_URI); } @Test - public void testOddDimensionsPlayback() throws ExoPlaybackException { + public void testOddDimensionsPlayback() throws Exception { playUri(BEAR_ODD_DIMENSIONS_URI); } @Test - public void test10BitProfile2Playback() throws ExoPlaybackException { + public void test10BitProfile2Playback() throws Exception { if (VpxLibrary.isHighBitDepthSupported()) { Log.d(TAG, "High Bit Depth supported."); playUri(ROADTRIP_10BIT_URI); @@ -87,16 +87,12 @@ public class VpxPlaybackTest { } } - private void playUri(String uri) throws ExoPlaybackException { + private void playUri(String uri) throws Exception { TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), getContext()); Thread thread = new Thread(testPlaybackRunnable); thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - fail(); // Should never happen. - } + thread.join(); if (testPlaybackRunnable.playbackException != null) { throw testPlaybackRunnable.playbackException; } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java index 49329c38c0..45b784e30f 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java @@ -87,7 +87,7 @@ public final class ContentDataSourceTest { fail(); } catch (ContentDataSource.ContentDataSourceException e) { // Expected. - assertThat(e.getCause()).isInstanceOf(FileNotFoundException.class); + assertThat(e).hasCauseThat().isInstanceOf(FileNotFoundException.class); } finally { dataSource.close(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index c4bbc1a53e..144fa76b33 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -686,24 +686,20 @@ public final class C { public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; - /** - * "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. - */ + /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ + @SuppressWarnings("ConstantField") public static final String CENC_TYPE_cenc = "cenc"; - /** - * "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016. - */ + /** "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016. */ + @SuppressWarnings("ConstantField") public static final String CENC_TYPE_cbc1 = "cbc1"; - /** - * "cens" scheme type name as defined in ISO/IEC 23001-7:2016. - */ + /** "cens" scheme type name as defined in ISO/IEC 23001-7:2016. */ + @SuppressWarnings("ConstantField") public static final String CENC_TYPE_cens = "cens"; - /** - * "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016. - */ + /** "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016. */ + @SuppressWarnings("ConstantField") public static final String CENC_TYPE_cbcs = "cbcs"; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 32722ae41e..57c55b0070 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -191,6 +191,14 @@ public interface Player { */ void clearVideoSurface(); + /** + * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surface The surface to clear. + */ + void clearVideoSurface(Surface surface); + /** * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for * tracking the lifecycle of the surface, and must clear the surface by calling {@code @@ -206,14 +214,6 @@ public interface Player { */ void setVideoSurface(@Nullable Surface surface); - /** - * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. - * Else does nothing. - * - * @param surface The surface to clear. - */ - void clearVideoSurface(Surface surface); - /** * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * rendered. The player will track the lifecycle of the surface automatically. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index fb7145aad8..d2b8d72b1e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -156,6 +156,14 @@ public final class PlayerMessage { return handler; } + /** + * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, + * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. + */ + public long getPositionMs() { + return positionMs; + } + /** * Sets a position in the current window at which the message will be delivered. * @@ -170,14 +178,6 @@ public final class PlayerMessage { return this; } - /** - * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, - * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. - */ - public long getPositionMs() { - return positionMs; - } - /** * Sets a position in a window at which the message will be delivered. * 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 a86a8d4d79..a86644c370 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 @@ -286,6 +286,13 @@ public class SimpleExoPlayer setVideoSurface(null); } + @Override + public void clearVideoSurface(Surface surface) { + if (surface != null && surface == this.surface) { + setVideoSurface(null); + } + } + @Override public void setVideoSurface(Surface surface) { removeSurfaceCallbacks(); @@ -294,13 +301,6 @@ public class SimpleExoPlayer maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } - @Override - public void clearVideoSurface(Surface surface) { - if (surface != null && surface == this.surface) { - setVideoSurface(null); - } - } - @Override public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) { removeSurfaceCallbacks(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 3a158060cf..f1a4b31e13 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -304,6 +304,11 @@ public class AnalyticsCollector // VideoListener implementation. + @Override + public final void onRenderedFirstFrame() { + // Do nothing. Already reported in VideoRendererEventListener.onRenderedFirstFrame. + } + @Override public final void onVideoSizeChanged( int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { @@ -322,11 +327,6 @@ public class AnalyticsCollector } } - @Override - public final void onRenderedFirstFrame() { - // Do nothing. Already reported in VideoRendererEventListener.onRenderedFirstFrame. - } - // MediaSourceEventListener implementation. @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index 501d87d208..7a32ef128b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -90,8 +90,8 @@ public class DecoderInputBuffer extends Buffer { /** * Ensures that {@link #data} is large enough to accommodate a write of a given length at its * current position. - *

- * If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is + * + *

If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is * insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer} * whose capacity is sufficient. Data up to the current position is copied to the new buffer. * @@ -99,7 +99,7 @@ public class DecoderInputBuffer extends Buffer { * @throws IllegalStateException If there is insufficient capacity to accommodate the write and * the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. */ - public void ensureSpaceForWrite(int length) throws IllegalStateException { + public void ensureSpaceForWrite(int length) { if (data == null) { data = createReplacementByteBuffer(length); return; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java index 98b1c7ca0f..7e5ae694ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java @@ -20,11 +20,11 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import java.util.ArrayDeque; -/** - * Base class for {@link Decoder}s that use their own decode thread. - */ -public abstract class SimpleDecoder implements Decoder { +/** Base class for {@link Decoder}s that use their own decode thread. */ +@SuppressWarnings("UngroupedOverloads") +public abstract class SimpleDecoder< + I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception> + implements Decoder { private final Thread decodeThread; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java index f394a7415c..ab49ca5454 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java @@ -153,7 +153,9 @@ public final class MpegAudioHeader { } int padding = (headerData >>> 9) & 1; - int bitrate, frameSize, samplesPerFrame; + int bitrate; + int frameSize; + int samplesPerFrame; if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index f59214fc37..3d33e105e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@SuppressWarnings("ConstantField") /* package*/ abstract class Atom { /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index fe79185697..6fa3a5fe2b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -38,9 +38,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -/** - * Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. - */ +/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */ +@SuppressWarnings("ConstantField") /* package */ final class AtomParsers { /** Thrown if an edit list couldn't be applied. */ @@ -619,9 +618,11 @@ import java.util.List; long timescale = mdhd.readUnsignedInt(); mdhd.skipBytes(version == 0 ? 4 : 8); int languageCode = mdhd.readUnsignedShort(); - String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) - + (char) (((languageCode >> 5) & 0x1F) + 0x60) - + (char) (((languageCode) & 0x1F) + 0x60); + String language = + "" + + (char) (((languageCode >> 10) & 0x1F) + 0x60) + + (char) (((languageCode >> 5) & 0x1F) + 0x60) + + (char) ((languageCode & 0x1F) + 0x60); return Pair.create(timescale, language); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index f253016cf7..e06eee8515 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -102,7 +102,10 @@ public final class FragmentedMp4Extractor implements Extractor { public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1 << 4; // 16 private static final String TAG = "FragmentedMp4Extractor"; + + @SuppressWarnings("ConstantField") private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); + private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; private static final Format EMSG_FORMAT = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java index d136468faa..8872c6359f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java @@ -26,9 +26,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; -/** - * StreamReader abstract class. - */ +/** StreamReader abstract class. */ +@SuppressWarnings("UngroupedOverloads") /* package */ abstract class StreamReader { private static final int STATE_READ_HEADERS = 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java index 31ac6858be..147ad5a20b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java @@ -153,7 +153,7 @@ import java.util.ArrayList; buffer.setLimit(buffer.limit() + 4); // The vorbis decoder expects the number of samples in the packet // to be appended to the audio data as an int32 - buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); + buffer.data[buffer.limit() - 4] = (byte) (packetSampleCount & 0xFF); buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index 0c59606ded..316c17bb34 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -169,6 +169,8 @@ public final class AdtsReader implements ElementaryStreamReader { case STATE_READING_SAMPLE: readSample(data); break; + default: + throw new IllegalStateException(); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index 0fc3383015..2e45853951 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -111,6 +111,8 @@ public final class DtsReader implements ElementaryStreamReader { state = STATE_FINDING_SYNC; } break; + default: + throw new IllegalStateException(); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java index 313e556764..f401a6e736 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java @@ -134,6 +134,8 @@ public final class LatmReader implements ElementaryStreamReader { state = STATE_FINDING_SYNC_1; } break; + default: + throw new IllegalStateException(); } } } @@ -250,6 +252,8 @@ public final class LatmReader implements ElementaryStreamReader { case 7: data.skipBits(1); // HVXCframeLengthTableIndex. break; + default: + throw new IllegalStateException(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index 82fb84b291..effa7d7c96 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -100,6 +100,8 @@ public final class MpegAudioReader implements ElementaryStreamReader { case STATE_READING_FRAME: readFrameRemainder(data); break; + default: + throw new IllegalStateException(); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java index 4863df42eb..6ae810a27d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java @@ -100,6 +100,8 @@ public final class PesReader implements TsPayloadReader { // Either way, notify the reader that it has now finished. reader.packetFinished(); break; + default: + throw new IllegalStateException(); } setState(STATE_READING_HEADER); } @@ -140,6 +142,8 @@ public final class PesReader implements TsPayloadReader { } } break; + default: + throw new IllegalStateException(); } } } 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 570d5074b7..65207401ff 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 @@ -499,23 +499,34 @@ public final class MediaCodecUtil { */ private static int avcLevelToMaxFrameSize(int avcLevel) { switch (avcLevel) { - case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16; - case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16; - case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16; - case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16; - case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16; - case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16; - case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16; - case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16; - case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16; - case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; - case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; - case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; - case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16; - default: return -1; + case CodecProfileLevel.AVCLevel1: + case CodecProfileLevel.AVCLevel1b: + return 99 * 16 * 16; + case CodecProfileLevel.AVCLevel12: + case CodecProfileLevel.AVCLevel13: + case CodecProfileLevel.AVCLevel2: + return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel21: + return 792 * 16 * 16; + case CodecProfileLevel.AVCLevel22: + case CodecProfileLevel.AVCLevel3: + return 1620 * 16 * 16; + case CodecProfileLevel.AVCLevel31: + return 3600 * 16 * 16; + case CodecProfileLevel.AVCLevel32: + return 5120 * 16 * 16; + case CodecProfileLevel.AVCLevel4: + case CodecProfileLevel.AVCLevel41: + return 8192 * 16 * 16; + case CodecProfileLevel.AVCLevel42: + return 8704 * 16 * 16; + case CodecProfileLevel.AVCLevel5: + return 22080 * 16 * 16; + case CodecProfileLevel.AVCLevel51: + case CodecProfileLevel.AVCLevel52: + return 36864 * 16 * 16; + default: + return -1; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java index 7e5125e71c..c2adda88e5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -32,6 +32,7 @@ import java.util.Arrays; */ public final class EventMessageDecoder implements MetadataDecoder { + @SuppressWarnings("ByteBufferBackingArray") @Override public Metadata decode(MetadataInputBuffer inputBuffer) { ByteBuffer buffer = inputBuffer.data; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 50a443aff3..289bcc3f1a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -96,6 +96,7 @@ public final class Id3Decoder implements MetadataDecoder { this.framePredicate = framePredicate; } + @SuppressWarnings("ByteBufferBackingArray") @Override public @Nullable Metadata decode(MetadataInputBuffer inputBuffer) { ByteBuffer buffer = inputBuffer.data; @@ -696,14 +697,13 @@ public final class Id3Decoder implements MetadataDecoder { */ private static String getCharsetName(int encodingByte) { switch (encodingByte) { - case ID3_TEXT_ENCODING_ISO_8859_1: - return "ISO-8859-1"; case ID3_TEXT_ENCODING_UTF_16: return "UTF-16"; case ID3_TEXT_ENCODING_UTF_16BE: return "UTF-16BE"; case ID3_TEXT_ENCODING_UTF_8: return "UTF-8"; + case ID3_TEXT_ENCODING_ISO_8859_1: default: return "ISO-8859-1"; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java index d6fc4f6c19..1153f918fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java @@ -44,6 +44,7 @@ public final class SpliceInfoDecoder implements MetadataDecoder { sectionHeader = new ParsableBitArray(); } + @SuppressWarnings("ByteBufferBackingArray") @Override public Metadata decode(MetadataInputBuffer inputBuffer) { // Internal timestamps adjustment. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java index 20b7860784..efe537a014 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadAction.java @@ -189,6 +189,7 @@ public abstract class DownloadAction { public abstract Downloader createDownloader( DownloaderConstructorHelper downloaderConstructorHelper); + @SuppressWarnings("EqualsGetClass") @Override public boolean equals(@Nullable Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 9aa7afd7cd..625ec4f5e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheUtil; import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters; import com.google.android.exoplayer2.util.PriorityTaskManager; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -54,8 +55,7 @@ public abstract class SegmentDownloader> impleme @Override public int compareTo(@NonNull Segment other) { - long startOffsetDiff = startTimeUs - other.startTimeUs; - return startOffsetDiff == 0 ? 0 : ((startOffsetDiff < 0) ? -1 : 1); + return Util.compareLong(startTimeUs, other.startTimeUs); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java index 997f750b61..38d6ff25cb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java @@ -67,9 +67,10 @@ public abstract class SimpleSubtitleDecoder extends super.releaseOutputBuffer(buffer); } + @SuppressWarnings("ByteBufferBackingArray") @Override - protected final SubtitleDecoderException decode(SubtitleInputBuffer inputBuffer, - SubtitleOutputBuffer outputBuffer, boolean reset) { + protected final SubtitleDecoderException decode( + SubtitleInputBuffer inputBuffer, SubtitleOutputBuffer outputBuffer, boolean reset) { try { ByteBuffer inputData = inputBuffer.data; Subtitle subtitle = decode(inputData.array(), inputData.limit(), reset); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 725321e53f..60cdda06c4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -250,6 +250,7 @@ public final class Cea608Decoder extends CeaDecoder { return new CeaSubtitle(cues); } + @SuppressWarnings("ByteBufferBackingArray") @Override protected void decode(SubtitleInputBuffer inputBuffer) { ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java index 3f201bccea..9a15cbae14 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java @@ -186,7 +186,7 @@ public abstract class BaseTrackSelection implements TrackSelection { // Track groups are compared by identity not value, as distinct groups may have the same value. @Override - @SuppressWarnings("ReferenceEquality") + @SuppressWarnings({"ReferenceEquality", "EqualsGetClass"}) public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java index 273509e0d4..30051a53e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java @@ -148,6 +148,7 @@ public final class ContentDataSource extends BaseDataSource { return uri; } + @SuppressWarnings("Finally") @Override public void close() throws ContentDataSourceException { uri = null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java index f86ed87c19..7f51efda0f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java @@ -171,6 +171,7 @@ public final class RawResourceDataSource extends BaseDataSource { return uri; } + @SuppressWarnings("Finally") @Override public void close() throws RawResourceDataSourceException { uri = null; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index db46ee4912..b041b0e03d 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -431,6 +431,7 @@ import com.google.android.exoplayer2.util.Util; * latter only checks the text of each sequence, and does not check for equality of styling that * may be embedded within the {@link CharSequence}s. */ + @SuppressWarnings("UndefinedEquals") private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { // Some CharSequence implementations don't perform a cheap referential equality check in their // equals methods, so we perform one explicitly here. From 23817eecfd77ddac87dbcba2ccb9aef1e459b178 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 28 Aug 2018 03:01:02 -0700 Subject: [PATCH 39/42] Release zip inflater when finished ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210513776 --- .../exoplayer2/video/spherical/ProjectionDecoder.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java index 4ef87bddfb..7a3c4998b3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.java @@ -121,8 +121,13 @@ public final class ProjectionDecoder { int encoding = input.readInt(); if (encoding == TYPE_DFL8) { ParsableByteArray output = new ParsableByteArray(); - if (!Util.inflate(input, output, new Inflater(true))) { - return null; + Inflater inflater = new Inflater(true); + try { + if (!Util.inflate(input, output, inflater)) { + return null; + } + } finally { + inflater.end(); } input = output; } else if (encoding != TYPE_RAW) { From efe8f09f45fe7804e8c92089f744d717502f9019 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 28 Aug 2018 06:10:42 -0700 Subject: [PATCH 40/42] Propagate EoS in renderer when using video tunneling ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210529471 --- RELEASENOTES.md | 3 +++ .../mediacodec/MediaCodecRenderer.java | 21 +++++++++++++------ .../video/MediaCodecVideoRenderer.java | 6 ++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 29d7b4dcaa..9d2c06a918 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,6 +39,9 @@ * Scale up the initial video decoder maximum input size so playlist item transitions with small increases in maximum sample size don't require reinitialization ([#4510](https://github.com/google/ExoPlayer/issues/4510)). + * Propagate the end-of-stream signal directly in the renderer when using + tunneling, to fix an issue where the player would remain ready after the + stream ended. * Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when creating a `CacheDataSource`. * Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend 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 1fbe058fb6..7daafe98ec 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 @@ -292,12 +292,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsFlushWorkaround; - private boolean codecNeedsEosPropagationWorkaround; private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosOutputExceptionWorkaround; private boolean codecNeedsMonoChannelCountWorkaround; private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer; + private boolean codecNeedsEosPropagation; private ByteBuffer[] inputBuffers; private ByteBuffer[] outputBuffers; private long codecHotswapDeadlineMs; @@ -468,10 +468,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); - codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecInfo); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); + codecNeedsEosPropagation = + codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation(); codecHotswapDeadlineMs = getState() == STATE_STARTED ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) @@ -486,6 +487,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return true; } + /** + * Returns whether the codec needs the renderer to propagate the end-of-stream signal directly, + * rather than by using an end-of-stream buffer queued to the codec. + */ + protected boolean getCodecNeedsEosPropagation() { + return false; + } + protected final MediaCodec getCodec() { return codec; } @@ -553,11 +562,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecNeedsDiscardToSpsWorkaround = false; codecNeedsFlushWorkaround = false; codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; - codecNeedsEosPropagationWorkaround = false; codecNeedsEosFlushWorkaround = false; codecNeedsMonoChannelCountWorkaround = false; codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; + codecNeedsEosPropagation = false; codecReceivedEos = false; codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; @@ -855,7 +864,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { // We need to re-initialize the codec. Send an end of stream signal to the existing codec so // that it outputs any remaining buffers before we release it. - if (codecNeedsEosPropagationWorkaround) { + if (codecNeedsEosPropagation) { // Do nothing. } else { codecReceivedEos = true; @@ -923,7 +932,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return false; } try { - if (codecNeedsEosPropagationWorkaround) { + if (codecNeedsEosPropagation) { // Do nothing. } else { codecReceivedEos = true; @@ -1254,7 +1263,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return true; } /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ - if (codecNeedsEosPropagationWorkaround + if (codecNeedsEosPropagation && (inputStreamEnded || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { processEndOfStream(); 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 0c3cd74b74..d1cda57ce0 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 @@ -447,6 +447,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return surface != null || shouldUseDummySurface(codecInfo); } + @Override + protected boolean getCodecNeedsEosPropagation() { + // In tunneling mode we can't dequeue an end-of-stream buffer, so propagate it in the renderer. + return tunneling; + } + @Override protected void configureCodec( MediaCodecInfo codecInfo, From ce1d8d6ce2a5f87348b474214fc8a05af44d26ca Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 28 Aug 2018 06:33:42 -0700 Subject: [PATCH 41/42] Remove obsolete SDK_INT modification ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210531569 --- .../main/java/com/google/android/exoplayer2/util/Util.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 494dc3ab24..ba17168d06 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -85,9 +85,7 @@ public final class Util { * Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently * overridden for local testing. */ - public static final int SDK_INT = - (Build.VERSION.SDK_INT == 25 && Build.VERSION.CODENAME.charAt(0) == 'O') ? 26 - : Build.VERSION.SDK_INT; + public static final int SDK_INT = Build.VERSION.SDK_INT; /** * Like {@link Build#DEVICE}, but in a place where it can be conveniently overridden for local From a429f4819efedcf970853b578eda7959c03ea22f Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 28 Aug 2018 07:28:41 -0700 Subject: [PATCH 42/42] Use camera motion metadata to stabilize 360 videos RELNOTES=true ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210537375 --- RELEASENOTES.md | 3 +- .../java/com/google/android/exoplayer2/C.java | 71 ++++------ .../exoplayer2/DefaultRenderersFactory.java | 33 ++++- .../com/google/android/exoplayer2/Player.java | 16 +++ .../android/exoplayer2/SimpleExoPlayer.java | 32 +++++ .../android/exoplayer2/util/EventLogger.java | 2 + .../android/exoplayer2/util/MimeTypes.java | 5 +- .../exoplayer2/util/TimedValueQueue.java | 2 +- .../google/android/exoplayer2/util/Util.java | 2 + .../video/spherical/CameraMotionListener.java | 32 +++++ .../video/spherical/CameraMotionRenderer.java | 129 +++++++++++++++++ .../video/spherical/FrameRotationQueue.java | 121 ++++++++++++++++ .../spherical/FrameRotationQueueTest.java | 133 ++++++++++++++++++ .../android/exoplayer2/ui/PlayerView.java | 4 +- .../ui/spherical/SceneRenderer.java | 33 ++++- .../ui/spherical/SphericalSurfaceView.java | 39 ++++- 16 files changed, 593 insertions(+), 64 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9d2c06a918..3098bd9a76 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -98,7 +98,8 @@ * Use default Deserializers if non given to DownloadManager. * 360: * Add monoscopic 360 surface type to PlayerView. - * Support VR180 videos. + * Support + [VR180 video format](https://github.com/google/spatial-media/blob/master/docs/vr180.md). * Deprecate `Player.DefaultEventListener` as selective listener overrides can be directly made with the `Player.EventListener` interface. * Deprecate `DefaultAnalyticsListener` as selective listener overrides can be diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 144fa76b33..0cbdc14b1c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.UUID; @@ -595,34 +596,22 @@ public final class C { */ public static final int DATA_TYPE_CUSTOM_BASE = 10000; - /** - * A type constant for tracks of unknown type. - */ + /** A type constant for tracks of unknown type. */ public static final int TRACK_TYPE_UNKNOWN = -1; - /** - * A type constant for tracks of some default type, where the type itself is unknown. - */ + /** A type constant for tracks of some default type, where the type itself is unknown. */ public static final int TRACK_TYPE_DEFAULT = 0; - /** - * A type constant for audio tracks. - */ + /** A type constant for audio tracks. */ public static final int TRACK_TYPE_AUDIO = 1; - /** - * A type constant for video tracks. - */ + /** A type constant for video tracks. */ public static final int TRACK_TYPE_VIDEO = 2; - /** - * A type constant for text tracks. - */ + /** A type constant for text tracks. */ public static final int TRACK_TYPE_TEXT = 3; - /** - * A type constant for metadata tracks. - */ + /** A type constant for metadata tracks. */ public static final int TRACK_TYPE_METADATA = 4; - /** - * A type constant for a dummy or empty track. - */ - public static final int TRACK_TYPE_NONE = 5; + /** A type constant for camera motion tracks. */ + public static final int TRACK_TYPE_CAMERA_MOTION = 5; + /** A type constant for a dummy or empty track. */ + public static final int TRACK_TYPE_NONE = 6; /** * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or * equal to this value. @@ -655,36 +644,27 @@ public final class C { */ public static final int SELECTION_REASON_CUSTOM_BASE = 10000; - /** - * A default size in bytes for an individual allocation that forms part of a larger buffer. - */ + /** A default size in bytes for an individual allocation that forms part of a larger buffer. */ public static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024; - /** - * A default size in bytes for a video buffer. - */ + /** A default size in bytes for a video buffer. */ public static final int DEFAULT_VIDEO_BUFFER_SIZE = 200 * DEFAULT_BUFFER_SEGMENT_SIZE; - /** - * A default size in bytes for an audio buffer. - */ + /** A default size in bytes for an audio buffer. */ public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE; - /** - * A default size in bytes for a text buffer. - */ + /** A default size in bytes for a text buffer. */ public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; - /** - * A default size in bytes for a metadata buffer. - */ + /** A default size in bytes for a metadata buffer. */ public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; - /** - * A default size in bytes for a muxed buffer (e.g. containing video, audio and text). - */ - public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE - + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + /** A default size in bytes for a camera motion buffer. */ + public static final int DEFAULT_CAMERA_MOTION_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for a muxed buffer (e.g. containing video, audio and text). */ + public static final int DEFAULT_MUXED_BUFFER_SIZE = + DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ @SuppressWarnings("ConstantField") @@ -798,6 +778,13 @@ public final class C { */ public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = 6; + /** + * The type of a message that can be passed to a camera motion {@link Renderer} via {@link + * ExoPlayer#createMessage(Target)}. The message payload should be a {@link CameraMotionListener} + * instance, or null. + */ + public static final int MSG_SET_CAMERA_MOTION_LISTENER = 7; + /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * {@link Renderer}s. These custom constants must be greater than or equal to this value. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index ff7e953896..c0a117c241 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; @@ -182,6 +183,7 @@ public class DefaultRenderersFactory implements RenderersFactory { extensionRendererMode, renderersList); buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), extensionRendererMode, renderersList); + buildCameraMotionRenderers(context, extensionRendererMode, renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); return renderersList.toArray(new Renderer[renderersList.size()]); } @@ -360,12 +362,14 @@ public class DefaultRenderersFactory implements RenderersFactory { * * @param context The {@link Context} associated with the player. * @param output An output for the renderers. - * @param outputLooper The looper associated with the thread on which the output should be - * called. + * @param outputLooper The looper associated with the thread on which the output should be called. * @param extensionRendererMode The extension renderer mode. * @param out An array to which the built renderers should be appended. */ - protected void buildTextRenderers(Context context, TextOutput output, Looper outputLooper, + protected void buildTextRenderers( + Context context, + TextOutput output, + Looper outputLooper, @ExtensionRendererMode int extensionRendererMode, ArrayList out) { out.add(new TextRenderer(output, outputLooper)); @@ -376,16 +380,31 @@ public class DefaultRenderersFactory implements RenderersFactory { * * @param context The {@link Context} associated with the player. * @param output An output for the renderers. - * @param outputLooper The looper associated with the thread on which the output should be - * called. + * @param outputLooper The looper associated with the thread on which the output should be called. * @param extensionRendererMode The extension renderer mode. * @param out An array to which the built renderers should be appended. */ - protected void buildMetadataRenderers(Context context, MetadataOutput output, Looper outputLooper, - @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + protected void buildMetadataRenderers( + Context context, + MetadataOutput output, + Looper outputLooper, + @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { out.add(new MetadataRenderer(output, outputLooper)); } + /** + * Builds camera motion renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildCameraMotionRenderers( + Context context, @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + out.add(new CameraMotionRenderer()); + } + /** * Builds any miscellaneous renderers used by the player. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 57c55b0070..e99d62a417 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -185,6 +186,21 @@ public interface Player { */ void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener); + /** + * Sets a listener of camera motion events. + * + * @param listener The listener. + */ + void setCameraMotionListener(CameraMotionListener listener); + + /** + * Clears the listener which receives camera motion events if it matches the one passed. Else + * does nothing. + * + * @param listener The listener to clear. + */ + void clearCameraMotionListener(CameraMotionListener listener); + /** * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} * currently set on the player. 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 a86644c370..c29055ddc7 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 @@ -53,6 +53,7 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -107,6 +108,7 @@ public class SimpleExoPlayer private MediaSource mediaSource; private List currentCues; private VideoFrameMetadataListener videoFrameMetadataListener; + private CameraMotionListener cameraMotionListener; /** * @param context A {@link Context}. @@ -597,6 +599,36 @@ public class SimpleExoPlayer } } + @Override + public void setCameraMotionListener(CameraMotionListener listener) { + cameraMotionListener = listener; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_CAMERA_MOTION) { + player + .createMessage(renderer) + .setType(C.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(listener) + .send(); + } + } + } + + @Override + public void clearCameraMotionListener(CameraMotionListener listener) { + if (cameraMotionListener != listener) { + return; + } + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_CAMERA_MOTION) { + player + .createMessage(renderer) + .setType(C.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(null) + .send(); + } + } + } + /** * Sets a listener to receive video events, removing all existing listeners. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index f9bd139298..626464ec69 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -599,6 +599,8 @@ public class EventLogger implements AnalyticsListener { return "default"; case C.TRACK_TYPE_METADATA: return "metadata"; + case C.TRACK_TYPE_CAMERA_MOTION: + return "camera motion"; case C.TRACK_TYPE_NONE: return "none"; case C.TRACK_TYPE_TEXT: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index e0b1df7739..f56aac7c70 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -328,9 +328,10 @@ public final class MimeTypes { return C.TRACK_TYPE_TEXT; } else if (APPLICATION_ID3.equals(mimeType) || APPLICATION_EMSG.equals(mimeType) - || APPLICATION_SCTE35.equals(mimeType) - || APPLICATION_CAMERA_MOTION.equals(mimeType)) { + || APPLICATION_SCTE35.equals(mimeType)) { return C.TRACK_TYPE_METADATA; + } else if (APPLICATION_CAMERA_MOTION.equals(mimeType)) { + return C.TRACK_TYPE_CAMERA_MOTION; } else { return getTrackTypeForCustomMimeType(mimeType); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java index 160db74eda..3fe3c56c15 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/TimedValueQueue.java @@ -64,7 +64,7 @@ public final class TimedValueQueue { /** * Returns the value with the greatest timestamp which is less than or equal to the given - * timestamp. Removes all older values including the returned one from the buffer. + * timestamp. Removes all older values and the returned one from the buffer. * * @param timestamp The timestamp value. * @return The value with the greatest timestamp which is less than or equal to the given diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index ba17168d06..2f30612081 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1443,6 +1443,8 @@ public final class Util { return C.DEFAULT_TEXT_BUFFER_SIZE; case C.TRACK_TYPE_METADATA: return C.DEFAULT_METADATA_BUFFER_SIZE; + case C.TRACK_TYPE_CAMERA_MOTION: + return C.DEFAULT_CAMERA_MOTION_BUFFER_SIZE; default: throw new IllegalStateException(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java new file mode 100644 index 0000000000..33fc639412 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionListener.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +/** Listens camera motion. */ +public interface CameraMotionListener { + + /** + * Called when a new camera motion is read. This method is called on the playback thread. + * + * @param timeUs The presentation time of the data. + * @param rotation Angle axis orientation in radians representing the rotation from camera + * coordinate system to world coordinate system. + */ + void onCameraMotion(long timeUs, float[] rotation); + + /** Called when the camera motion track position is reset. */ + void onCameraMotionReset(); +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java new file mode 100644 index 0000000000..5fab84ed8d --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.BaseRenderer; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; +import java.nio.ByteBuffer; + +/** A {@link Renderer} that parses the camera motion track. */ +public class CameraMotionRenderer extends BaseRenderer { + + // The amount of time to read samples ahead of the current time. + private static final int SAMPLE_WINDOW_DURATION_US = 100000; + + private final FormatHolder formatHolder; + private final DecoderInputBuffer buffer; + private final ParsableByteArray scratch; + + private long offsetUs; + private @Nullable CameraMotionListener listener; + private long lastTimestampUs; + + public CameraMotionRenderer() { + super(C.TRACK_TYPE_CAMERA_MOTION); + formatHolder = new FormatHolder(); + buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + scratch = new ParsableByteArray(); + } + + @Override + public int supportsFormat(Format format) { + return MimeTypes.APPLICATION_CAMERA_MOTION.equals(format.sampleMimeType) + ? FORMAT_HANDLED + : FORMAT_UNSUPPORTED_TYPE; + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + if (messageType == C.MSG_SET_CAMERA_MOTION_LISTENER) { + listener = (CameraMotionListener) message; + } else { + super.handleMessage(messageType, message); + } + } + + @Override + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { + this.offsetUs = offsetUs; + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + lastTimestampUs = 0; + if (listener != null) { + listener.onCameraMotionReset(); + } + } + + @Override + protected void onDisabled() { + lastTimestampUs = 0; + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + // Keep reading available samples as long as the sample time is not too far into the future. + while (!hasReadStreamToEnd() && lastTimestampUs < positionUs + SAMPLE_WINDOW_DURATION_US) { + buffer.clear(); + int result = readSource(formatHolder, buffer, /* formatRequired= */ false); + if (result != C.RESULT_BUFFER_READ || buffer.isEndOfStream()) { + return; + } + + buffer.flip(); + lastTimestampUs = buffer.timeUs; + if (listener != null) { + float[] rotation = parseMetadata(buffer.data); + if (rotation != null) { + Util.castNonNull(listener).onCameraMotion(lastTimestampUs - offsetUs, rotation); + } + } + } + } + + @Override + public boolean isEnded() { + return hasReadStreamToEnd(); + } + + @Override + public boolean isReady() { + return true; + } + + private @Nullable float[] parseMetadata(ByteBuffer data) { + if (data.remaining() != 16) { + return null; + } + scratch.reset(data.array(), data.limit()); + scratch.setPosition(data.arrayOffset() + 4); // skip reserved bytes too. + float[] result = new float[3]; + for (int i = 0; i < 3; i++) { + result[i] = Float.intBitsToFloat(scratch.readLittleEndianInt()); + } + return result; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java new file mode 100644 index 0000000000..d7404cbce4 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import android.opengl.Matrix; +import com.google.android.exoplayer2.util.TimedValueQueue; + +/** + * This class serves multiple purposes: + * + *

    + *
  • Queues the rotation metadata extracted from camera motion track. + *
  • Converts the metadata to rotation matrices in OpenGl coordinate system. + *
  • Recenters the rotations to componsate the yaw of the initial rotation. + *
+ */ +public final class FrameRotationQueue { + private final float[] recenterMatrix; + private final float[] rotationMatrix; + private final TimedValueQueue rotations; + private boolean recenterMatrixComputed; + + public FrameRotationQueue() { + recenterMatrix = new float[16]; + rotationMatrix = new float[16]; + rotations = new TimedValueQueue<>(); + } + + /** + * Sets a rotation for a given timestamp. + * + * @param timestampUs Timestamp of the rotation. + * @param angleAxis Angle axis orientation in radians representing the rotation from camera + * coordinate system to world coordinate system. + */ + public void setRotation(long timestampUs, float[] angleAxis) { + rotations.add(timestampUs, angleAxis); + } + + /** Removes all of the rotations and forces rotations to be recentered. */ + public void reset() { + rotations.clear(); + recenterMatrixComputed = false; + } + + /** + * Copies the rotation matrix with the greatest timestamp which is less than or equal to the given + * timestamp to {@code matrix}. Removes all older rotations and the returned one from the queue. + * Does nothing if there is no such rotation. + * + * @param matrix A float array to hold the rotation matrix. + * @param timestampUs The time in microseconds to query the rotation. + * @return Whether a rotation matrix is copied to {@code matrix}. + */ + public boolean pollRotationMatrix(float[] matrix, long timestampUs) { + float[] rotation = rotations.pollFloor(timestampUs); + if (rotation == null) { + return false; + } + // TODO [Internal: b/113315546]: Slerp between the floor and ceil rotation. + getRotationMatrixFromAngleAxis(rotationMatrix, rotation); + if (!recenterMatrixComputed) { + computeRecenterMatrix(recenterMatrix, rotationMatrix); + recenterMatrixComputed = true; + } + Matrix.multiplyMM(matrix, 0, recenterMatrix, 0, rotationMatrix, 0); + return true; + } + + /** + * Computes a recentering matrix from the given angle-axis rotation only accounting for yaw. Roll + * and tilt will not be compensated. + */ + private static void computeRecenterMatrix(float[] recenterMatrix, float[] rotationMatrix) { + // The re-centering matrix is computed as follows: + // recenter.row(2) = temp.col(2).transpose(); + // recenter.row(0) = recenter.row(1).cross(recenter.row(2)).normalized(); + // recenter.row(2) = recenter.row(0).cross(recenter.row(1)).normalized(); + // | temp[10] 0 -temp[8] 0| + // | 0 1 0 0| + // recenter = | temp[8] 0 temp[10] 0| + // | 0 0 0 1| + Matrix.setIdentityM(recenterMatrix, 0); + float normRowSqr = + rotationMatrix[10] * rotationMatrix[10] + rotationMatrix[8] * rotationMatrix[8]; + float normRow = (float) Math.sqrt(normRowSqr); + recenterMatrix[0] = rotationMatrix[10] / normRow; + recenterMatrix[2] = rotationMatrix[8] / normRow; + recenterMatrix[8] = -rotationMatrix[8] / normRow; + recenterMatrix[10] = rotationMatrix[10] / normRow; + } + + private static void getRotationMatrixFromAngleAxis(float[] matrix, float[] angleAxis) { + // Convert coordinates to OpenGL coordinates. + // CAMM motion metadata: +x right, +y down, and +z forward. + // OpenGL: +x right, +y up, -z forwards + float x = angleAxis[0]; + float y = -angleAxis[1]; + float z = -angleAxis[2]; + float angleRad = Matrix.length(x, y, z); + if (angleRad != 0) { + float angleDeg = (float) Math.toDegrees(angleRad); + Matrix.setRotateM(matrix, 0, angleDeg, x / angleRad, y / angleRad, z / angleRad); + } else { + Matrix.setIdentityM(matrix, 0); + } + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java new file mode 100644 index 0000000000..071cd582d5 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video.spherical; + +import static com.google.common.truth.Truth.assertThat; + +import android.opengl.Matrix; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests {@link FrameRotationQueue}. */ +@RunWith(RobolectricTestRunner.class) +public class FrameRotationQueueTest { + + private FrameRotationQueue frameRotationQueue; + private float[] rotationMatrix; + + @Before + public void setUp() throws Exception { + frameRotationQueue = new FrameRotationQueue(); + rotationMatrix = new float[16]; + } + + @Test + public void testGetRotationMatrixReturnsNull_whenEmpty() throws Exception { + assertThat(frameRotationQueue.pollRotationMatrix(rotationMatrix, 0)).isFalse(); + } + + @Test + public void testGetRotationMatrixReturnsNotNull_whenNotEmpty() throws Exception { + frameRotationQueue.setRotation(0, new float[] {1, 2, 3}); + assertThat(frameRotationQueue.pollRotationMatrix(rotationMatrix, 0)).isTrue(); + assertThat(rotationMatrix).hasLength(16); + } + + @Test + public void testConvertsAngleAxisToRotationMatrix() throws Exception { + doTestAngleAxisToRotationMatrix(/* angleRadian= */ 0, /* x= */ 1, /* y= */ 0, /* z= */ 0); + frameRotationQueue.reset(); + doTestAngleAxisToRotationMatrix(/* angleRadian= */ 1, /* x= */ 1, /* y= */ 0, /* z= */ 0); + frameRotationQueue.reset(); + doTestAngleAxisToRotationMatrix(/* angleRadian= */ 1, /* x= */ 0, /* y= */ 0, /* z= */ 1); + // Don't reset frameRotationQueue as we use recenter matrix from previous calls. + doTestAngleAxisToRotationMatrix(/* angleRadian= */ -1, /* x= */ 0, /* y= */ 1, /* z= */ 0); + doTestAngleAxisToRotationMatrix(/* angleRadian= */ 1, /* x= */ 1, /* y= */ 1, /* z= */ 1); + } + + @Test + public void testRecentering_justYaw() throws Exception { + float[] actualMatrix = + getRotationMatrixFromAngleAxis( + /* angleRadian= */ (float) Math.PI, /* x= */ 0, /* y= */ 1, /* z= */ 0); + float[] expectedMatrix = new float[16]; + Matrix.setIdentityM(expectedMatrix, 0); + assertEquals(actualMatrix, expectedMatrix); + } + + @Test + public void testRecentering_yawAndPitch() throws Exception { + float[] matrix = + getRotationMatrixFromAngleAxis( + /* angleRadian= */ (float) Math.PI, /* x= */ 1, /* y= */ 1, /* z= */ 0); + assertMultiplication( + /* xr= */ 0, /* yr= */ 0, /* zr= */ 1, matrix, /* x= */ 0, /* y= */ 0, /* z= */ 1); + } + + @Test + public void testRecentering_yawAndPitch2() throws Exception { + float[] matrix = + getRotationMatrixFromAngleAxis( + /* angleRadian= */ (float) Math.PI / 2, /* x= */ 1, /* y= */ 1, /* z= */ 0); + float sqrt2 = (float) Math.sqrt(2); + assertMultiplication( + /* xr= */ sqrt2, /* yr= */ 0, /* zr= */ 0, matrix, /* x= */ 1, /* y= */ -1, /* z= */ 0); + } + + @Test + public void testRecentering_yawAndPitchAndRoll() throws Exception { + float[] matrix = + getRotationMatrixFromAngleAxis( + /* angleRadian= */ (float) Math.PI * 2 / 3, /* x= */ 1, /* y= */ 1, /* z= */ 1); + assertMultiplication( + /* xr= */ 0, /* yr= */ 0, /* zr= */ 1, matrix, /* x= */ 0, /* y= */ 0, /* z= */ 1); + } + + private void doTestAngleAxisToRotationMatrix(float angleRadian, int x, int y, int z) { + float[] actualMatrix = getRotationMatrixFromAngleAxis(angleRadian, x, y, z); + float[] expectedMatrix = createRotationMatrix(angleRadian, x, y, z); + assertEquals(actualMatrix, expectedMatrix); + } + + private float[] getRotationMatrixFromAngleAxis(float angleRadian, int x, int y, int z) { + float length = Matrix.length(x, y, z); + float factor = angleRadian / length; + // Negate y and z to revert OpenGL coordinate system conversion. + frameRotationQueue.setRotation(0, new float[] {x * factor, -y * factor, -z * factor}); + frameRotationQueue.pollRotationMatrix(rotationMatrix, 0); + return rotationMatrix; + } + + private static void assertMultiplication( + float xr, float yr, float zr, float[] actualMatrix, float x, float y, float z) { + float[] vector = new float[] {x, y, z, 0}; + float[] resultVec = new float[4]; + Matrix.multiplyMV(resultVec, 0, actualMatrix, 0, vector, 0); + assertEquals(resultVec, new float[] {xr, yr, zr, 0}); + } + + private static float[] createRotationMatrix(float angleRadian, int x, int y, int z) { + float[] expectedMatrix = new float[16]; + Matrix.setRotateM(expectedMatrix, 0, (float) Math.toDegrees(angleRadian), x, y, z); + return expectedMatrix; + } + + private static void assertEquals(float[] actual, float[] expected) { + assertThat(actual).usingTolerance(1.0e-5).containsExactly(expected).inOrder(); + } +} 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 8ad8899216..142b251a61 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 @@ -515,6 +515,7 @@ public class PlayerView extends FrameLayout { } else if (surfaceView instanceof SphericalSurfaceView) { oldVideoComponent.clearVideoSurface(((SphericalSurfaceView) surfaceView).getSurface()); oldVideoComponent.clearVideoFrameMetadataListener(((SphericalSurfaceView) surfaceView)); + oldVideoComponent.clearCameraMotionListener(((SphericalSurfaceView) surfaceView)); } else if (surfaceView instanceof SurfaceView) { oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView); } @@ -540,8 +541,9 @@ public class PlayerView extends FrameLayout { if (surfaceView instanceof TextureView) { newVideoComponent.setVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SphericalSurfaceView) { - newVideoComponent.setVideoSurface(((SphericalSurfaceView) surfaceView).getSurface()); newVideoComponent.setVideoFrameMetadataListener(((SphericalSurfaceView) surfaceView)); + newVideoComponent.setCameraMotionListener(((SphericalSurfaceView) surfaceView)); + newVideoComponent.setVideoSurface(((SphericalSurfaceView) surfaceView).getSurface()); } else if (surfaceView instanceof SurfaceView) { newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index d529de1ccb..023d68f988 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -19,9 +19,12 @@ import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; import android.graphics.SurfaceTexture; import android.opengl.GLES20; +import android.opengl.Matrix; import android.support.annotation.Nullable; import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.TimedValueQueue; +import com.google.android.exoplayer2.video.spherical.FrameRotationQueue; import com.google.android.exoplayer2.video.spherical.Projection; import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -35,17 +38,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final AtomicBoolean frameAvailable; private final ProjectionRenderer projectionRenderer; + private final FrameRotationQueue frameRotationQueue; + private final TimedValueQueue sampleTimestampQueue; + private final float[] rotationMatrix; + private final float[] tempMatrix; private int textureId; private @MonotonicNonNull SurfaceTexture surfaceTexture; private @Nullable Projection pendingProjection; private long pendingProjectionTimeNs; private long lastFrameTimestamp; + private boolean resetRotationAtNextFrame; - public SceneRenderer(Projection projection) { + public SceneRenderer( + Projection projection, + FrameRotationQueue frameRotationQueue, + TimedValueQueue sampleTimestampQueue) { + this.frameRotationQueue = frameRotationQueue; + this.sampleTimestampQueue = sampleTimestampQueue; frameAvailable = new AtomicBoolean(); projectionRenderer = new ProjectionRenderer(); projectionRenderer.setProjection(projection); + rotationMatrix = new float[16]; + tempMatrix = new float[16]; + resetRotation(); } /** Initializes the renderer. */ @@ -63,6 +79,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return surfaceTexture; } + public void resetRotation() { + resetRotationAtNextFrame = true; + } + /** Sets a {@link Projection} to be used to display video. */ public void setProjection(Projection projection, long timeNs) { pendingProjection = projection; @@ -84,13 +104,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (frameAvailable.compareAndSet(true, false)) { Assertions.checkNotNull(surfaceTexture).updateTexImage(); checkGlError(); + if (resetRotationAtNextFrame) { + Matrix.setIdentityM(rotationMatrix, 0); + } lastFrameTimestamp = surfaceTexture.getTimestamp(); + Long sampleTimestamp = sampleTimestampQueue.poll(lastFrameTimestamp); + if (sampleTimestamp != null) { + frameRotationQueue.pollRotationMatrix(rotationMatrix, sampleTimestamp); + } } if (pendingProjection != null && pendingProjectionTimeNs <= lastFrameTimestamp) { projectionRenderer.setProjection(pendingProjection); pendingProjection = null; } - - projectionRenderer.draw(textureId, viewProjectionMatrix, eyeType); + Matrix.multiplyMM(tempMatrix, 0, viewProjectionMatrix, 0, rotationMatrix, 0); + projectionRenderer.draw(textureId, tempMatrix, eyeType); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index 483376dba4..30995aca5f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -40,8 +40,11 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import com.google.android.exoplayer2.video.spherical.FrameRotationQueue; import com.google.android.exoplayer2.video.spherical.Projection; import com.google.android.exoplayer2.video.spherical.ProjectionDecoder; import java.util.Arrays; @@ -60,7 +63,7 @@ import javax.microedition.khronos.opengles.GL10; */ @TargetApi(15) public final class SphericalSurfaceView extends GLSurfaceView - implements VideoFrameMetadataListener { + implements VideoFrameMetadataListener, CameraMotionListener { /** * This listener can be used to be notified when the {@link Surface} associated with this view is @@ -91,6 +94,8 @@ public final class SphericalSurfaceView extends GLSurfaceView private final PhoneOrientationListener phoneOrientationListener; private final Renderer renderer; private final Handler mainHandler; + private final TimedValueQueue sampleTimestampQueue; + private final FrameRotationQueue frameRotationQueue; private final TouchTracker touchTracker; private @Nullable SurfaceListener surfaceListener; private @Nullable SurfaceTexture surfaceTexture; @@ -105,9 +110,6 @@ public final class SphericalSurfaceView extends GLSurfaceView public SphericalSurfaceView(Context context, @Nullable AttributeSet attributeSet) { super(context, attributeSet); - - defaultStereoMode = C.STEREO_MODE_MONO; - currentStereoMode = C.STEREO_MODE_MONO; mainHandler = new Handler(Looper.getMainLooper()); // Configure sensors and touch. @@ -120,7 +122,13 @@ public final class SphericalSurfaceView extends GLSurfaceView int type = Util.SDK_INT >= 18 ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_ROTATION_VECTOR; orientationSensor = sensorManager.getDefaultSensor(type); - renderer = new Renderer(); + defaultStereoMode = C.STEREO_MODE_MONO; + currentStereoMode = defaultStereoMode; + Projection projection = Projection.createEquirectangular(defaultStereoMode); + frameRotationQueue = new FrameRotationQueue(); + sampleTimestampQueue = new TimedValueQueue<>(); + SceneRenderer scene = new SceneRenderer(projection, frameRotationQueue, sampleTimestampQueue); + renderer = new Renderer(scene); touchTracker = new TouchTracker(context, renderer, PX_PER_DEGREES); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); @@ -161,12 +169,29 @@ public final class SphericalSurfaceView extends GLSurfaceView touchTracker.setSingleTapListener(listener); } + // VideoFrameMetadataListener implementation. + @Override public void onVideoFrameAboutToBeRendered( long presentationTimeUs, long releaseTimeNs, Format format) { + sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs); setProjection(format.projectionData, format.stereoMode, releaseTimeNs); } + // CameraMotionListener implementation. + + @Override + public void onCameraMotion(long timeUs, float[] rotation) { + frameRotationQueue.setRotation(timeUs, rotation); + } + + @Override + public void onCameraMotionReset() { + sampleTimestampQueue.clear(); + frameRotationQueue.reset(); + queueEvent(renderer.scene::resetRotation); + } + @Override public void onResume() { super.onResume(); @@ -354,8 +379,8 @@ public final class SphericalSurfaceView extends GLSurfaceView private final float[] viewMatrix = new float[16]; private final float[] tempMatrix = new float[16]; - public Renderer() { - scene = new SceneRenderer(Projection.createEquirectangular(C.STEREO_MODE_MONO)); + public Renderer(SceneRenderer scene) { + this.scene = scene; Matrix.setIdentityM(deviceOrientationMatrix, 0); Matrix.setIdentityM(touchPitchMatrix, 0); Matrix.setIdentityM(touchYawMatrix, 0);