Merge branch 'dev-v2' of https://github.com/google/ExoPlayer into dev-v2

This commit is contained in:
Sebastian Roth 2018-08-29 17:54:18 +08:00
commit ebb027031e
No known key found for this signature in database
GPG key ID: 85A9F39F2F7877D4
260 changed files with 5166 additions and 3457 deletions

View file

@ -39,6 +39,9 @@
* Scale up the initial video decoder maximum input size so playlist item * Scale up the initial video decoder maximum input size so playlist item
transitions with small increases in maximum sample size don't require transitions with small increases in maximum sample size don't require
reinitialization ([#4510](https://github.com/google/ExoPlayer/issues/4510)). 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 * Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when
creating a `CacheDataSource`. creating a `CacheDataSource`.
* Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend * Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend
@ -70,6 +73,8 @@
* Allow configuration of the Loader retry delay * Allow configuration of the Loader retry delay
([#3370](https://github.com/google/ExoPlayer/issues/3370)). ([#3370](https://github.com/google/ExoPlayer/issues/3370)).
* HLS: * HLS:
* Add support for variable substitution
([#4422](https://github.com/google/ExoPlayer/issues/4422)).
* Add support for PlayReady. * Add support for PlayReady.
* Add support for alternative EXT-X-KEY tags. * Add support for alternative EXT-X-KEY tags.
* Set the bitrate on primary track sample formats * Set the bitrate on primary track sample formats
@ -91,7 +96,10 @@
* Allow setting the `Looper`, which is used to access the player, in * Allow setting the `Looper`, which is used to access the player, in
`ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)). `ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)).
* Use default Deserializers if non given to DownloadManager. * Use default Deserializers if non given to DownloadManager.
* 360:
* Add monoscopic 360 surface type to PlayerView. * Add monoscopic 360 surface type to PlayerView.
* Support
[VR180 video format](https://github.com/google/spatial-media/blob/master/docs/vr180.md).
* Deprecate `Player.DefaultEventListener` as selective listener overrides can * Deprecate `Player.DefaultEventListener` as selective listener overrides can
be directly made with the `Player.EventListener` interface. be directly made with the `Player.EventListener` interface.
* Deprecate `DefaultAnalyticsListener` as selective listener overrides can be * Deprecate `DefaultAnalyticsListener` as selective listener overrides can be
@ -99,6 +107,10 @@
* Add uri field to `LoadEventInfo` in `MediaSourceEventListener` or * Add uri field to `LoadEventInfo` in `MediaSourceEventListener` or
`AnalyticsListener` callbacks. This uri is the redirected uri if redirection `AnalyticsListener` callbacks. This uri is the redirected uri if redirection
occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)). 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 * Allow `MediaCodecSelector`s to return multiple compatible decoders for
`MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that `MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that
falls back to less preferred decoders like `MediaCodec.createDecoderByType` falls back to less preferred decoders like `MediaCodec.createDecoderByType`
@ -110,6 +122,14 @@
* Add option to show buffering view when playWhenReady is false * Add option to show buffering view when playWhenReady is false
([#4304](https://github.com/google/ExoPlayer/issues/4304)). ([#4304](https://github.com/google/ExoPlayer/issues/4304)).
* Allow any `Drawable` to be used as `PlayerView` default artwork. * 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 ### ### 2.8.4 ###

View file

@ -30,8 +30,6 @@ import android.view.Menu;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
@ -145,13 +143,9 @@ public class MainActivity extends AppCompatActivity implements OnClickListener,
ListView sampleList = dialogList.findViewById(R.id.sample_list); ListView sampleList = dialogList.findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleListAdapter(this)); sampleList.setAdapter(new SampleListAdapter(this));
sampleList.setOnItemClickListener( sampleList.setOnItemClickListener(
new OnItemClickListener() { (parent, view, position, id) -> {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
playerManager.addItem(DemoUtil.SAMPLES.get(position)); playerManager.addItem(DemoUtil.SAMPLES.get(position));
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1); mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
}
}); });
return dialogList; return dialogList;
} }

View file

@ -76,9 +76,7 @@ import com.google.android.exoplayer2.util.Util;
contentMediaSource, contentMediaSource,
/* adMediaSourceFactory= */ this, /* adMediaSourceFactory= */ this,
adsLoader, adsLoader,
playerView.getOverlayFrameLayout(), playerView.getOverlayFrameLayout());
/* eventHandler= */ null,
/* eventListener= */ null);
// Prepare the player with the source. // Prepare the player with the source.
player.seekTo(contentPosition); player.seekTo(contentPosition);

View file

@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-feature android:name="android.software.leanback" android:required="false"/> <uses-feature android:name="android.software.leanback" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>

View file

@ -567,6 +567,11 @@
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4",
"spherical_stereo_mode": "top_bottom" "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)", "name": "Iceland (360 top-bottom stereo ts)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts",

View file

@ -175,15 +175,12 @@ public class DownloadTracker implements DownloadManager.Listener {
} }
final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]); final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]);
actionFileWriteHandler.post( actionFileWriteHandler.post(
new Runnable() { () -> {
@Override
public void run() {
try { try {
actionFile.store(actions); actionFile.store(actions);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to store tracked actions", e); Log.e(TAG, "Failed to store tracked actions", e);
} }
}
}); });
} }

View file

@ -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.DashMediaSource;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; 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.SsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
@ -190,7 +190,7 @@ public class PlayerActivity extends Activity
finish(); finish();
return; return;
} }
((SphericalSurfaceView) playerView.getVideoSurfaceView()).setStereoMode(stereoMode); ((SphericalSurfaceView) playerView.getVideoSurfaceView()).setDefaultStereoMode(stereoMode);
} }
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -490,8 +490,8 @@ public class PlayerActivity extends Activity
.createMediaSource(uri); .createMediaSource(uri);
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory) return new HlsMediaSource.Factory(dataSourceFactory)
.setPlaylistParser( .setPlaylistParserFactory(
new FilteringManifestParser<>(new HlsPlaylistParser(), getOfflineStreamKeys(uri))) new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri)))
.createMediaSource(uri); .createMediaSource(uri);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);

View file

@ -22,6 +22,7 @@ import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.JsonReader; import android.util.JsonReader;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
@ -162,8 +163,8 @@ public class SampleChooserActivity extends Activity
startActivity( startActivity(
sample.buildIntent( sample.buildIntent(
/* context= */ this, /* context= */ this,
preferExtensionDecodersMenuItem.isChecked(), isNonNullAndChecked(preferExtensionDecodersMenuItem),
randomAbrMenuItem.isChecked() isNonNullAndChecked(randomAbrMenuItem)
? PlayerActivity.ABR_ALGORITHM_RANDOM ? PlayerActivity.ABR_ALGORITHM_RANDOM
: PlayerActivity.ABR_ALGORITHM_DEFAULT)); : PlayerActivity.ABR_ALGORITHM_DEFAULT));
return true; return true;
@ -198,6 +199,11 @@ public class SampleChooserActivity extends Activity
return 0; 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<String, Void, List<SampleGroup>> { private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> {
private boolean sawError; private boolean sawError;
@ -207,7 +213,8 @@ public class SampleChooserActivity extends Activity
List<SampleGroup> result = new ArrayList<>(); List<SampleGroup> result = new ArrayList<>();
Context context = getApplicationContext(); Context context = getApplicationContext();
String userAgent = Util.getUserAgent(context, "ExoPlayerDemo"); 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) { for (String uri : uris) {
DataSpec dataSpec = new DataSpec(Uri.parse(uri)); DataSpec dataSpec = new DataSpec(Uri.parse(uri));
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);

View file

@ -50,8 +50,6 @@ dependencies {
api 'com.android.support:recyclerview-v7:' + supportLibraryVersion api 'com.android.support:recyclerview-v7:' + supportLibraryVersion
} }
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
ext { ext {
javadocTitle = 'Cast extension' javadocTitle = 'Cast extension'
} }

View file

@ -567,6 +567,11 @@ public final class CastPlayer implements Player {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
@Override
public long getContentDuration() {
return getDuration();
}
@Override @Override
public boolean isLoading() { public boolean isLoading() {
return false; return false;
@ -839,7 +844,6 @@ public final class CastPlayer implements Player {
@Override @Override
public void onAdBreakStatusUpdated() {} public void onAdBreakStatusUpdated() {}
// SessionManagerListener implementation. // SessionManagerListener implementation.
@Override @Override

View file

@ -606,11 +606,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
if (request != currentUrlRequest) { if (request != currentUrlRequest) {
return; return;
} }
if (currentDataSpec.postBody != null) { if (currentDataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) {
int responseCode = info.getHttpStatusCode(); int responseCode = info.getHttpStatusCode();
// The industry standard is to disregard POST redirects when the status code is 307 or 308. // 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) { if (responseCode == 307 || responseCode == 308) {
exception = exception =
new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec); new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec);
@ -627,7 +625,23 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
request.followRedirect(); request.followRedirect();
} else { } else {
currentUrlRequest.cancel(); 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; UrlRequest.Builder requestBuilder;
try { try {
requestBuilder = buildRequestBuilder(redirectUrlDataSpec); requestBuilder = buildRequestBuilder(redirectUrlDataSpec);

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cronet;
import android.content.Context; import android.content.Context;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -39,7 +40,8 @@ public final class CronetEngineWrapper {
private final @CronetEngineSource int cronetEngineSource; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE}) @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE})
@ -158,6 +160,8 @@ public final class CronetEngineWrapper {
private final String gmsCoreCronetName; private final String gmsCoreCronetName;
private final boolean preferGMSCoreCronet; private final boolean preferGMSCoreCronet;
// Multi-catch can only be used for API 19+ in this case.
@SuppressWarnings("UseMultiCatch")
public CronetProviderComparator(boolean preferGMSCoreCronet) { public CronetProviderComparator(boolean preferGMSCoreCronet) {
// GMSCore CronetProvider classes are only available in some configurations. // GMSCore CronetProvider classes are only available in some configurations.
// Thus, we use reflection to copy static name. // Thus, we use reflection to copy static name.
@ -218,8 +222,8 @@ public final class CronetEngineWrapper {
if (versionLeft == null || versionRight == null) { if (versionLeft == null || versionRight == null) {
return 0; return 0;
} }
String[] versionStringsLeft = versionLeft.split("\\."); String[] versionStringsLeft = Util.split(versionLeft, "\\.");
String[] versionStringsRight = versionRight.split("\\."); String[] versionStringsRight = Util.split(versionRight, "\\.");
int minLength = Math.min(versionStringsLeft.length, versionStringsRight.length); int minLength = Math.min(versionStringsLeft.length, versionStringsRight.length);
for (int i = 0; i < minLength; i++) { for (int i = 0; i < minLength; i++) {
if (!versionStringsLeft[i].equals(versionStringsRight[i])) { if (!versionStringsLeft[i].equals(versionStringsRight[i])) {

View file

@ -60,7 +60,7 @@ public final class ByteArrayUploadDataProviderTest {
@Test @Test
public void testReadPartialBuffer() throws IOException { 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); byte[] secondHalf = Arrays.copyOfRange(TEST_DATA, TEST_DATA.length / 2, TEST_DATA.length);
byteBuffer = ByteBuffer.allocate(TEST_DATA.length / 2); byteBuffer = ByteBuffer.allocate(TEST_DATA.length / 2);
// Read half of the data. // Read half of the data.

View file

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceExcep
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Predicate;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -50,6 +51,7 @@ import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.chromium.net.CronetEngine; import org.chromium.net.CronetEngine;
import org.chromium.net.NetworkException; import org.chromium.net.NetworkException;
import org.chromium.net.UrlRequest; import org.chromium.net.UrlRequest;
@ -60,10 +62,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowSystemClock;
/** Tests for {@link CronetDataSource}. */ /** Tests for {@link CronetDataSource}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@ -73,7 +72,7 @@ public final class CronetDataSourceTest {
private static final int TEST_READ_TIMEOUT_MS = 100; private static final int TEST_READ_TIMEOUT_MS = 100;
private static final String TEST_URL = "http://google.com"; private static final String TEST_URL = "http://google.com";
private static final String TEST_CONTENT_TYPE = "test/test"; 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 long TEST_CONTENT_LENGTH = 16000L;
private static final int TEST_CONNECTION_STATUS = 5; private static final int TEST_CONNECTION_STATUS = 5;
private static final int TEST_INVALID_CONNECTION_STATUS = -1; private static final int TEST_INVALID_CONNECTION_STATUS = -1;
@ -170,16 +169,13 @@ public final class CronetDataSourceTest {
final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); final UrlRequest mockUrlRequest2 = mock(UrlRequest.class);
when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2); when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2);
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
// Invoke the callback for the previous request. // Invoke the callback for the previous request.
dataSourceUnderTest.urlRequestCallback.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, testUrlResponseInfo, mockNetworkException); mockUrlRequest, testUrlResponseInfo, mockNetworkException);
dataSourceUnderTest.urlRequestCallback.onResponseStarted( dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest2, testUrlResponseInfo); mockUrlRequest2, testUrlResponseInfo);
return null; return null;
}
}) })
.when(mockUrlRequest2) .when(mockUrlRequest2)
.start(); .start();
@ -582,10 +578,10 @@ public final class CronetDataSourceTest {
// We should still be trying to open. // We should still be trying to open.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout. // 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); assertNotCountedDown(timedOutLatch);
// Now we timeout. // Now we timeout.
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10);
timedOutLatch.await(); timedOutLatch.await();
verify(mockTransferListener, never()) verify(mockTransferListener, never())
@ -621,7 +617,7 @@ public final class CronetDataSourceTest {
// We should still be trying to open. // We should still be trying to open.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout. // 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); assertNotCountedDown(timedOutLatch);
// Now we interrupt. // Now we interrupt.
thread.interrupt(); thread.interrupt();
@ -637,14 +633,16 @@ public final class CronetDataSourceTest {
final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch openLatch = new CountDownLatch(1); final CountDownLatch openLatch = new CountDownLatch(1);
AtomicReference<Exception> exceptionOnTestThread = new AtomicReference<>();
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
try { try {
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
openLatch.countDown();
} catch (HttpDataSourceException e) { } catch (HttpDataSourceException e) {
fail(); exceptionOnTestThread.set(e);
} finally {
openLatch.countDown();
} }
} }
}.start(); }.start();
@ -653,11 +651,12 @@ public final class CronetDataSourceTest {
// We should still be trying to open. // We should still be trying to open.
assertNotCountedDown(openLatch); assertNotCountedDown(openLatch);
// We should still be trying to open as we approach the timeout. // 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); assertNotCountedDown(openLatch);
// The response arrives just in time. // The response arrives just in time.
dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
openLatch.await(); openLatch.await();
assertThat(exceptionOnTestThread.get()).isNull();
} }
@Test @Test
@ -687,14 +686,14 @@ public final class CronetDataSourceTest {
// We should still be trying to open. // We should still be trying to open.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout. // 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); assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time. // A redirect arrives just in time.
dataSourceUnderTest.urlRequestCallback.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1");
long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; 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. // We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time. // A redirect arrives just in time.
@ -702,11 +701,11 @@ public final class CronetDataSourceTest {
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2");
newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; 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. // We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// Now we timeout. // Now we timeout.
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10); SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10);
timedOutLatch.await(); timedOutLatch.await();
verify(mockTransferListener, never()) verify(mockTransferListener, never())
@ -900,14 +899,11 @@ public final class CronetDataSourceTest {
private void mockStatusResponse() { private void mockStatusResponse() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
UrlRequest.StatusListener statusListener = UrlRequest.StatusListener statusListener =
(UrlRequest.StatusListener) invocation.getArguments()[0]; (UrlRequest.StatusListener) invocation.getArguments()[0];
statusListener.onStatus(TEST_CONNECTION_STATUS); statusListener.onStatus(TEST_CONNECTION_STATUS);
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.getStatus(any(UrlRequest.StatusListener.class)); .getStatus(any(UrlRequest.StatusListener.class));
@ -915,13 +911,10 @@ public final class CronetDataSourceTest {
private void mockResponseStartSuccess() { private void mockResponseStartSuccess() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.urlRequestCallback.onResponseStarted( dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo); mockUrlRequest, testUrlResponseInfo);
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.start(); .start();
@ -929,15 +922,12 @@ public final class CronetDataSourceTest {
private void mockResponseStartRedirect() { private void mockResponseStartRedirect() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.urlRequestCallback.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(307), // statusCode createUrlResponseInfo(307), // statusCode
"http://redirect.location.com"); "http://redirect.location.com");
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.start(); .start();
@ -945,9 +935,7 @@ public final class CronetDataSourceTest {
private void mockSingleRedirectSuccess() { private void mockSingleRedirectSuccess() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!redirectCalled) { if (!redirectCalled) {
redirectCalled = true; redirectCalled = true;
dataSourceUnderTest.urlRequestCallback.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
@ -959,7 +947,6 @@ public final class CronetDataSourceTest {
mockUrlRequest, testUrlResponseInfo); mockUrlRequest, testUrlResponseInfo);
} }
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.start(); .start();
@ -967,13 +954,10 @@ public final class CronetDataSourceTest {
private void mockFollowRedirectSuccess() { private void mockFollowRedirectSuccess() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.urlRequestCallback.onResponseStarted( dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo); mockUrlRequest, testUrlResponseInfo);
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.followRedirect(); .followRedirect();
@ -981,15 +965,12 @@ public final class CronetDataSourceTest {
private void mockResponseStartFailure() { private void mockResponseStartFailure() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.urlRequestCallback.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(500), // statusCode createUrlResponseInfo(500), // statusCode
mockNetworkException); mockNetworkException);
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.start(); .start();
@ -998,9 +979,7 @@ public final class CronetDataSourceTest {
private void mockReadSuccess(int position, int length) { private void mockReadSuccess(int position, int length) {
final int[] positionAndRemaining = new int[] {position, length}; final int[] positionAndRemaining = new int[] {position, length};
doAnswer( doAnswer(
new Answer<Void>() { invocation -> {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (positionAndRemaining[1] == 0) { if (positionAndRemaining[1] == 0) {
dataSourceUnderTest.urlRequestCallback.onSucceeded( dataSourceUnderTest.urlRequestCallback.onSucceeded(
mockUrlRequest, testUrlResponseInfo); mockUrlRequest, testUrlResponseInfo);
@ -1014,7 +993,6 @@ public final class CronetDataSourceTest {
mockUrlRequest, testUrlResponseInfo, inputBuffer); mockUrlRequest, testUrlResponseInfo, inputBuffer);
} }
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.read(any(ByteBuffer.class)); .read(any(ByteBuffer.class));
@ -1022,15 +1000,12 @@ public final class CronetDataSourceTest {
private void mockReadFailure() { private void mockReadFailure() {
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.urlRequestCallback.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(500), // statusCode createUrlResponseInfo(500), // statusCode
mockNetworkException); mockNetworkException);
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.read(any(ByteBuffer.class)); .read(any(ByteBuffer.class));
@ -1039,12 +1014,9 @@ public final class CronetDataSourceTest {
private ConditionVariable buildReadStartedCondition() { private ConditionVariable buildReadStartedCondition() {
final ConditionVariable startedCondition = new ConditionVariable(); final ConditionVariable startedCondition = new ConditionVariable();
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open(); startedCondition.open();
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.read(any(ByteBuffer.class)); .read(any(ByteBuffer.class));
@ -1054,12 +1026,9 @@ public final class CronetDataSourceTest {
private ConditionVariable buildUrlRequestStartedCondition() { private ConditionVariable buildUrlRequestStartedCondition() {
final ConditionVariable startedCondition = new ConditionVariable(); final ConditionVariable startedCondition = new ConditionVariable();
doAnswer( doAnswer(
new Answer<Object>() { invocation -> {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open(); startedCondition.open();
return null; return null;
}
}) })
.when(mockUrlRequest) .when(mockUrlRequest)
.start(); .start();

View file

@ -27,6 +27,7 @@ android {
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
sourceSets.main { sourceSets.main {
@ -38,6 +39,7 @@ android {
dependencies { dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
androidTestImplementation 'androidx.test:runner:' + testRunnerVersion
androidTestImplementation project(modulePrefix + 'testutils') androidTestImplementation project(modulePrefix + 'testutils')
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
} }

View file

@ -26,6 +26,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.flac.test" android:targetPackage="com.google.android.exoplayer2.ext.flac.test"
android:name="android.test.InstrumentationTestRunner"/> android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest> </manifest>

View file

@ -16,9 +16,7 @@
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import android.test.InstrumentationTestCase; 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;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link FlacExtractor}. * Unit test for {@link FlacExtractor}.
@ -35,25 +33,11 @@ public class FlacExtractorTest extends InstrumentationTestCase {
public void testExtractFlacSample() throws Exception { public void testExtractFlacSample() throws Exception {
ExtractorAsserts.assertBehavior( ExtractorAsserts.assertBehavior(
new ExtractorFactory() { FlacExtractor::new, "bear.flac", getInstrumentation().getContext());
@Override
public Extractor create() {
return new FlacExtractor();
}
},
"bear.flac",
getInstrumentation().getContext());
} }
public void testExtractFlacSampleWithId3Header() throws Exception { public void testExtractFlacSampleWithId3Header() throws Exception {
ExtractorAsserts.assertBehavior( ExtractorAsserts.assertBehavior(
new ExtractorFactory() { FlacExtractor::new, "bear_with_id3.flac", getInstrumentation().getContext());
@Override
public Extractor create() {
return new FlacExtractor();
}
},
"bear_with_id3.flac",
getInstrumentation().getContext());
} }
} }

View file

@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.ext.flac; 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.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import androidx.test.runner.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
@ -29,36 +32,34 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 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}. */
* Playback tests using {@link LibflacAudioRenderer}. @RunWith(AndroidJUnit4.class)
*/ public class FlacPlaybackTest {
public class FlacPlaybackTest extends InstrumentationTestCase {
private static final String BEAR_FLAC_URI = "asset:///bear-flac.mka"; private static final String BEAR_FLAC_URI = "asset:///bear-flac.mka";
@Override @Before
protected void setUp() throws Exception { public void setUp() {
super.setUp();
if (!FlacLibrary.isAvailable()) { if (!FlacLibrary.isAvailable()) {
fail("Flac library not available."); fail("Flac library not available.");
} }
} }
public void testBasicPlayback() throws ExoPlaybackException { @Test
public void testBasicPlayback() throws Exception {
playUri(BEAR_FLAC_URI); 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), TestPlaybackRunnable testPlaybackRunnable =
getInstrumentation().getContext()); new TestPlaybackRunnable(Uri.parse(uri), getContext());
Thread thread = new Thread(testPlaybackRunnable); Thread thread = new Thread(testPlaybackRunnable);
thread.start(); thread.start();
try {
thread.join(); thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (testPlaybackRunnable.playbackException != null) { if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException; throw testPlaybackRunnable.playbackException;
} }

View file

@ -155,6 +155,7 @@ import java.nio.ByteBuffer;
} }
/** Decodes and consumes the next sample from the FLAC stream into the given byte buffer. */ /** Decodes and consumes the next sample from the FLAC stream into the given byte buffer. */
@SuppressWarnings("ByteBufferBackingArray")
public void decodeSample(ByteBuffer output) public void decodeSample(ByteBuffer output)
throws IOException, InterruptedException, FlacFrameDecodeException { throws IOException, InterruptedException, FlacFrameDecodeException {
output.clear(); output.clear();

View file

@ -50,7 +50,10 @@ public final class FlacExtractor implements Extractor {
/** Factory that returns one extractor which is a {@link FlacExtractor}. */ /** Factory that returns one extractor which is a {@link FlacExtractor}. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new 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) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,

View file

@ -46,13 +46,13 @@ public final class DefaultExtractorsFactoryTest {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors(); Extractor[] extractors = defaultExtractorsFactory.createExtractors();
List<Class> listCreatedExtractorClasses = new ArrayList<>(); List<Class<?>> listCreatedExtractorClasses = new ArrayList<>();
for (Extractor extractor : extractors) { for (Extractor extractor : extractors) {
listCreatedExtractorClasses.add(extractor.getClass()); listCreatedExtractorClasses.add(extractor.getClass());
} }
Class[] expectedExtractorClassses = Class<?>[] expectedExtractorClassses =
new Class[] { new Class<?>[] {
MatroskaExtractor.class, MatroskaExtractor.class,
FragmentedMp4Extractor.class, FragmentedMp4Extractor.class,
Mp4Extractor.class, Mp4Extractor.class,

View file

@ -48,8 +48,6 @@ dependencies {
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
} }
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
ext { ext {
javadocTitle = 'IMA extension' javadocTitle = 'IMA extension'
} }

View file

@ -631,11 +631,8 @@ public final class ImaAdsLoader
} else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs;
int adGroupIndexForPosition = expectedAdGroupIndex =
adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
if (adGroupIndexForPosition != C.INDEX_UNSET) {
expectedAdGroupIndex = adGroupIndexForPosition;
}
} else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) { } else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) {
contentPositionMs = player.getCurrentPosition(); contentPositionMs = player.getCurrentPosition();
// Update the expected ad group index for the current content position. The update is delayed // Update the expected ad group index for the current content position. The update is delayed
@ -867,8 +864,6 @@ public final class ImaAdsLoader
&& playWhenReady) { && playWhenReady) {
checkForContentComplete(); checkForContentComplete();
} else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) { } 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++) { for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded(); adCallbacks.get(i).onEnded();
} }
@ -1044,7 +1039,6 @@ public final class ImaAdsLoader
int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup; int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup;
playingAd = player.isPlayingAd(); playingAd = player.isPlayingAd();
playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET; playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET;
if (!sentContentComplete) {
boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup; boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup;
if (adFinished) { if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now. // IMA is waiting for the ad playback to finish so invoke the callback now.
@ -1056,7 +1050,7 @@ public final class ImaAdsLoader
Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity");
} }
} }
if (!wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) {
int adGroupIndex = player.getCurrentAdGroupIndex(); int adGroupIndex = player.getCurrentAdGroupIndex();
// IMA hasn't called playAd yet, so fake the content position. // IMA hasn't called playAd yet, so fake the content position.
fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime();
@ -1066,7 +1060,6 @@ public final class ImaAdsLoader
} }
} }
} }
}
private void resumeContentInternal() { private void resumeContentInternal() {
if (imaAdState != IMA_AD_STATE_NONE) { if (imaAdState != IMA_AD_STATE_NONE) {
@ -1127,16 +1120,8 @@ public final class ImaAdsLoader
if (pendingAdLoadError == null) { if (pendingAdLoadError == null) {
pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex); 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; pendingContentPositionMs = C.TIME_UNSET;
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
} }
private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) {
@ -1184,6 +1169,10 @@ public final class ImaAdsLoader
Log.d(TAG, "adsLoader.contentComplete"); Log.d(TAG, "adsLoader.contentComplete");
} }
sentContentComplete = true; 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));
} }
} }

View file

@ -27,9 +27,9 @@ import java.util.ArrayList;
private final ArrayList<Player.EventListener> listeners; private final ArrayList<Player.EventListener> listeners;
private final Timeline.Window window; private final Timeline.Window window;
private final Timeline.Period period; private final Timeline.Period period;
private final Timeline timeline;
private boolean prepared; private boolean prepared;
private Timeline timeline;
private int state; private int state;
private boolean playWhenReady; private boolean playWhenReady;
private long position; private long position;

View file

@ -23,7 +23,6 @@ import com.firebase.jobdispatcher.Constraint;
import com.firebase.jobdispatcher.FirebaseJobDispatcher; import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver; import com.firebase.jobdispatcher.GooglePlayDriver;
import com.firebase.jobdispatcher.Job; import com.firebase.jobdispatcher.Job;
import com.firebase.jobdispatcher.Job.Builder;
import com.firebase.jobdispatcher.JobParameters; import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService; import com.firebase.jobdispatcher.JobService;
import com.firebase.jobdispatcher.Lifetime; import com.firebase.jobdispatcher.Lifetime;
@ -38,6 +37,7 @@ import com.google.android.exoplayer2.util.Util;
* *
* <pre>{@literal * <pre>{@literal
* <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> * <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
* *
* <service * <service
* android:name="com.google.android.exoplayer2.ext.jobdispatcher.JobDispatcherScheduler$JobDispatcherSchedulerService" * android:name="com.google.android.exoplayer2.ext.jobdispatcher.JobDispatcherScheduler$JobDispatcherSchedulerService"
@ -98,7 +98,7 @@ public final class JobDispatcherScheduler implements Scheduler {
String tag, String tag,
String serviceAction, String serviceAction,
String servicePackage) { String servicePackage) {
Builder builder = Job.Builder builder =
dispatcher dispatcher
.newJobBuilder() .newJobBuilder()
.setService(JobDispatcherSchedulerService.class) // the JobService that will be called .setService(JobDispatcherSchedulerService.class) // the JobService that will be called

View file

@ -248,8 +248,6 @@ public final class MediaSessionConnector {
* description)}. * description)}.
*/ */
void onRemoveQueueItem(Player player, MediaDescriptionCompat 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. */ /** Callback receiving a user rating for the active media item. */
@ -1022,12 +1020,5 @@ public final class MediaSessionConnector {
queueEditor.onRemoveQueueItem(player, description); queueEditor.onRemoveQueueItem(player, description);
} }
} }
@Override
public void onRemoveQueueItemAt(int index) {
if (queueEditor != null) {
queueEditor.onRemoveQueueItemAt(player, index);
}
}
} }
} }

View file

@ -184,18 +184,13 @@ public final class TimelineQueueEditor
List<MediaSessionCompat.QueueItem> queue = mediaController.getQueue(); List<MediaSessionCompat.QueueItem> queue = mediaController.getQueue();
for (int i = 0; i < queue.size(); i++) { for (int i = 0; i < queue.size(); i++) {
if (equalityChecker.equals(queue.get(i).getDescription(), description)) { if (equalityChecker.equals(queue.get(i).getDescription(), description)) {
onRemoveQueueItemAt(player, i); queueDataAdapter.remove(i);
queueMediaSource.removeMediaSource(i);
return; return;
} }
} }
} }
@Override
public void onRemoveQueueItemAt(Player player, int index) {
queueDataAdapter.remove(index);
queueMediaSource.removeMediaSource(index);
}
// CommandReceiver implementation. // CommandReceiver implementation.
@NonNull @NonNull

View file

@ -161,8 +161,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
responseBody = Assertions.checkNotNull(response.body()); responseBody = Assertions.checkNotNull(response.body());
responseByteStream = responseBody.byteStream(); responseByteStream = responseBody.byteStream();
} catch (IOException e) { } catch (IOException e) {
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, throw new HttpDataSourceException(
dataSpec, HttpDataSourceException.TYPE_OPEN); "Unable to connect to " + dataSpec.uri, e, dataSpec, HttpDataSourceException.TYPE_OPEN);
} }
int responseCode = response.code(); int responseCode = response.code();

View file

@ -27,6 +27,7 @@ android {
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
sourceSets.main { sourceSets.main {
@ -37,6 +38,7 @@ android {
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
androidTestImplementation 'androidx.test:runner:' + testRunnerVersion
} }
ext { ext {

View file

@ -26,6 +26,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.opus.test" android:targetPackage="com.google.android.exoplayer2.ext.opus.test"
android:name="android.test.InstrumentationTestRunner"/> android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest> </manifest>

View file

@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.ext.opus; 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.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import androidx.test.runner.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
@ -29,36 +32,34 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 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}. */
* Playback tests using {@link LibopusAudioRenderer}. @RunWith(AndroidJUnit4.class)
*/ public class OpusPlaybackTest {
public class OpusPlaybackTest extends InstrumentationTestCase {
private static final String BEAR_OPUS_URI = "asset:///bear-opus.webm"; private static final String BEAR_OPUS_URI = "asset:///bear-opus.webm";
@Override @Before
protected void setUp() throws Exception { public void setUp() {
super.setUp();
if (!OpusLibrary.isAvailable()) { if (!OpusLibrary.isAvailable()) {
fail("Opus library not available."); fail("Opus library not available.");
} }
} }
public void testBasicPlayback() throws ExoPlaybackException { @Test
public void testBasicPlayback() throws Exception {
playUri(BEAR_OPUS_URI); 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), TestPlaybackRunnable testPlaybackRunnable =
getInstrumentation().getContext()); new TestPlaybackRunnable(Uri.parse(uri), getContext());
Thread thread = new Thread(testPlaybackRunnable); Thread thread = new Thread(testPlaybackRunnable);
thread.start(); thread.start();
try {
thread.join(); thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (testPlaybackRunnable.playbackException != null) { if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException; throw testPlaybackRunnable.playbackException;
} }

View file

@ -38,7 +38,11 @@ public final class RtmpDataSourceFactory implements DataSource.Factory {
@Override @Override
public DataSource createDataSource() { public DataSource createDataSource() {
return new RtmpDataSource(listener); RtmpDataSource dataSource = new RtmpDataSource();
if (listener != null) {
dataSource.addTransferListener(listener);
}
return dataSource;
} }
} }

View file

@ -27,6 +27,7 @@ android {
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
sourceSets.main { sourceSets.main {
@ -38,6 +39,7 @@ android {
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
androidTestImplementation 'androidx.test:runner:' + testRunnerVersion
androidTestImplementation 'com.google.truth:truth:' + truthVersion androidTestImplementation 'com.google.truth:truth:' + truthVersion
} }

View file

@ -26,6 +26,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.vp9.test" android:targetPackage="com.google.android.exoplayer2.ext.vp9.test"
android:name="android.test.InstrumentationTestRunner"/> android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest> </manifest>

View file

@ -15,13 +15,15 @@
*/ */
package com.google.android.exoplayer2.ext.vp9; package com.google.android.exoplayer2.ext.vp9;
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase;
import android.util.Log; import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; 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.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 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}. */
* Playback tests using {@link LibvpxVideoRenderer}. @RunWith(AndroidJUnit4.class)
*/ public class VpxPlaybackTest {
public class VpxPlaybackTest extends InstrumentationTestCase {
private static final String BEAR_URI = "asset:///bear-vp9.webm"; private static final String BEAR_URI = "asset:///bear-vp9.webm";
private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///bear-vp9-odd-dimensions.webm"; private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///bear-vp9-odd-dimensions.webm";
@ -45,23 +49,25 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
private static final String TAG = "VpxPlaybackTest"; private static final String TAG = "VpxPlaybackTest";
@Override @Before
protected void setUp() throws Exception { public void setUp() {
super.setUp();
if (!VpxLibrary.isAvailable()) { if (!VpxLibrary.isAvailable()) {
fail("Vpx library not available."); fail("Vpx library not available.");
} }
} }
public void testBasicPlayback() throws ExoPlaybackException { @Test
public void testBasicPlayback() throws Exception {
playUri(BEAR_URI); playUri(BEAR_URI);
} }
public void testOddDimensionsPlayback() throws ExoPlaybackException { @Test
public void testOddDimensionsPlayback() throws Exception {
playUri(BEAR_ODD_DIMENSIONS_URI); playUri(BEAR_ODD_DIMENSIONS_URI);
} }
public void test10BitProfile2Playback() throws ExoPlaybackException { @Test
public void test10BitProfile2Playback() throws Exception {
if (VpxLibrary.isHighBitDepthSupported()) { if (VpxLibrary.isHighBitDepthSupported()) {
Log.d(TAG, "High Bit Depth supported."); Log.d(TAG, "High Bit Depth supported.");
playUri(ROADTRIP_10BIT_URI); playUri(ROADTRIP_10BIT_URI);
@ -70,6 +76,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
Log.d(TAG, "High Bit Depth not supported."); Log.d(TAG, "High Bit Depth not supported.");
} }
@Test
public void testInvalidBitstream() { public void testInvalidBitstream() {
try { try {
playUri(INVALID_BITSTREAM_URI); playUri(INVALID_BITSTREAM_URI);
@ -80,16 +87,12 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
} }
private void playUri(String uri) throws ExoPlaybackException { private void playUri(String uri) throws Exception {
TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri), TestPlaybackRunnable testPlaybackRunnable =
getInstrumentation().getContext()); new TestPlaybackRunnable(Uri.parse(uri), getContext());
Thread thread = new Thread(testPlaybackRunnable); Thread thread = new Thread(testPlaybackRunnable);
thread.start(); thread.start();
try {
thread.join(); thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (testPlaybackRunnable.playbackException != null) { if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException; throw testPlaybackRunnable.playbackException;
} }

View file

@ -39,8 +39,10 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; 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.TraceUtil;
import com.google.android.exoplayer2.util.Util; 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;
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -109,11 +111,14 @@ public class LibvpxVideoRenderer extends BaseRenderer {
private final boolean playClearSamplesWithoutKeys; private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final TimedValueQueue<Format> formatQueue;
private final DecoderInputBuffer flagsOnlyBuffer; private final DecoderInputBuffer flagsOnlyBuffer;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager; private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final boolean useSurfaceYuvOutput; private final boolean useSurfaceYuvOutput;
private Format format; private Format format;
private Format pendingFormat;
private Format outputFormat;
private VpxDecoder decoder; private VpxDecoder decoder;
private VpxInputBuffer inputBuffer; private VpxInputBuffer inputBuffer;
private VpxOutputBuffer outputBuffer; private VpxOutputBuffer outputBuffer;
@ -142,6 +147,8 @@ public class LibvpxVideoRenderer extends BaseRenderer {
private int consecutiveDroppedFrameCount; private int consecutiveDroppedFrameCount;
private int buffersInCodecCount; private int buffersInCodecCount;
private long lastRenderTimeUs; private long lastRenderTimeUs;
private long outputStreamOffsetUs;
private VideoFrameMetadataListener frameMetadataListener;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
@ -219,6 +226,7 @@ public class LibvpxVideoRenderer extends BaseRenderer {
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
clearReportedVideoSize(); clearReportedVideoSize();
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
formatQueue = new TimedValueQueue<>();
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
outputMode = VpxDecoder.OUTPUT_MODE_NONE; outputMode = VpxDecoder.OUTPUT_MODE_NONE;
@ -328,6 +336,7 @@ public class LibvpxVideoRenderer extends BaseRenderer {
} else { } else {
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
} }
formatQueue.clear();
} }
@Override @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. * 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 { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format; Format oldFormat = format;
format = newFormat; format = newFormat;
pendingFormat = newFormat;
boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null
: oldFormat.drmInitData); : oldFormat.drmInitData);
@ -629,6 +645,8 @@ public class LibvpxVideoRenderer extends BaseRenderer {
setOutput((Surface) message, null); setOutput((Surface) message, null);
} else if (messageType == MSG_SET_OUTPUT_BUFFER_RENDERER) { } else if (messageType == MSG_SET_OUTPUT_BUFFER_RENDERER) {
setOutput(null, (VpxOutputBufferRenderer) message); setOutput(null, (VpxOutputBufferRenderer) message);
} else if (messageType == C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) {
frameMetadataListener = (VideoFrameMetadataListener) message;
} else { } else {
super.handleMessage(messageType, message); super.handleMessage(messageType, message);
} }
@ -772,6 +790,10 @@ public class LibvpxVideoRenderer extends BaseRenderer {
if (waitingForKeys) { if (waitingForKeys) {
return false; return false;
} }
if (pendingFormat != null) {
formatQueue.add(inputBuffer.timeUs, pendingFormat);
pendingFormat = null;
}
inputBuffer.flip(); inputBuffer.flip();
inputBuffer.colorInfo = formatHolder.format.colorInfo; inputBuffer.colorInfo = formatHolder.format.colorInfo;
onQueueInputBuffer(inputBuffer); onQueueInputBuffer(inputBuffer);
@ -851,11 +873,21 @@ public class LibvpxVideoRenderer extends BaseRenderer {
return false; return false;
} }
long presentationTimeUs = outputBuffer.timeUs - outputStreamOffsetUs;
Format format = formatQueue.pollFloor(presentationTimeUs);
if (format != null) {
outputFormat = format;
}
long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
boolean isStarted = getState() == STATE_STARTED; boolean isStarted = getState() == STATE_STARTED;
if (!renderedFirstFrame if (!renderedFirstFrame
|| (isStarted || (isStarted
&& shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) {
if (frameMetadataListener != null) {
frameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, System.nanoTime(), outputFormat);
}
renderOutputBuffer(outputBuffer); renderOutputBuffer(outputBuffer);
return true; return true;
} }
@ -873,6 +905,10 @@ public class LibvpxVideoRenderer extends BaseRenderer {
} }
if (earlyUs < 30000) { if (earlyUs < 30000) {
if (frameMetadataListener != null) {
frameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, System.nanoTime(), outputFormat);
}
renderOutputBuffer(outputBuffer); renderOutputBuffer(outputBuffer);
return true; return true;
} }

View file

@ -39,7 +39,7 @@ class CombinedJavadocPlugin implements Plugin<Project> {
libraryModules.each { libraryModule -> libraryModules.each { libraryModule ->
libraryModule.android.libraryVariants.all { variant -> libraryModule.android.libraryVariants.all { variant ->
def name = variant.buildType.name def name = variant.buildType.name
if (name.equals("release")) { if (name == "release") {
classpath += classpath +=
libraryModule.project.files( libraryModule.project.files(
variant.javaCompile.classpath.files, variant.javaCompile.classpath.files,
@ -63,7 +63,7 @@ class CombinedJavadocPlugin implements Plugin<Project> {
} }
// Returns Android library modules that declare a generateJavadoc task. // Returns Android library modules that declare a generateJavadoc task.
private Set<Project> getLibraryModules(Project project) { private static Set<Project> getLibraryModules(Project project) {
project.subprojects.findAll { project.subprojects.findAll {
it.plugins.findPlugin("com.android.library") && it.plugins.findPlugin("com.android.library") &&
it.tasks.findByName("generateJavadoc") it.tasks.findByName("generateJavadoc")

View file

@ -28,7 +28,7 @@ android {
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
// The following argument makes the Android Test Orchestrator run its // The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures // "pm clear" command after each test invocation. This command ensures
@ -39,11 +39,11 @@ android {
// Workaround to prevent circular dependency on project :testutils. // Workaround to prevent circular dependency on project :testutils.
sourceSets { sourceSets {
androidTest { androidTest {
java.srcDirs += "../../testutils/src/main/java/" java.srcDirs += '../../testutils/src/main/java/'
} }
test { test {
java.srcDirs += "../../testutils/src/main/java/" java.srcDirs += '../../testutils/src/main/java/'
java.srcDirs += "../../testutils_robolectric/src/main/java/" java.srcDirs += '../../testutils_robolectric/src/main/java/'
} }
} }
@ -60,12 +60,12 @@ dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-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:' + dexmakerVersion
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestImplementation 'com.google.truth:truth:' + truthVersion androidTestImplementation 'com.google.truth:truth:' + truthVersion
androidTestImplementation 'org.mockito:mockito-core:' + mockitoVersion 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 androidTestAnnotationProcessor 'com.google.auto.value:auto-value:' + autoValueVersion
testImplementation 'com.google.truth:truth:' + truthVersion testImplementation 'com.google.truth:truth:' + truthVersion
testImplementation 'junit:junit:' + junitVersion testImplementation 'junit:junit:' + junitVersion

View file

@ -29,6 +29,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.core.test" android:targetPackage="com.google.android.exoplayer2.core.test"
android:name="android.test.InstrumentationTestRunner"/> android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest> </manifest>

View file

@ -87,7 +87,7 @@ public final class ContentDataSourceTest {
fail(); fail();
} catch (ContentDataSource.ContentDataSourceException e) { } catch (ContentDataSource.ContentDataSourceException e) {
// Expected. // Expected.
assertThat(e.getCause()).isInstanceOf(FileNotFoundException.class); assertThat(e).hasCauseThat().isInstanceOf(FileNotFoundException.class);
} finally { } finally {
dataSource.close(); dataSource.close();
} }

View file

@ -26,6 +26,8 @@ import android.view.Surface;
import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.util.Util; 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.UUID; import java.util.UUID;
@ -109,7 +111,8 @@ public final class C {
public static final String SANS_SERIF_NAME = "sans-serif"; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
@ -133,7 +136,14 @@ public final class C {
*/ */
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
Format.NO_VALUE, Format.NO_VALUE,
@ -153,7 +163,12 @@ public final class C {
}) })
public @interface Encoding {} 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
Format.NO_VALUE, Format.NO_VALUE,
@ -195,11 +210,22 @@ public final class C {
public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_DTMF, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, @IntDef({
STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, STREAM_TYPE_USE_DEFAULT}) 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 {} public @interface StreamType {}
/** /**
* @see AudioManager#STREAM_ALARM * @see AudioManager#STREAM_ALARM
@ -239,11 +265,18 @@ public final class C {
public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, CONTENT_TYPE_SONIFICATION, CONTENT_TYPE_SPEECH, @IntDef({
CONTENT_TYPE_UNKNOWN}) CONTENT_TYPE_MOVIE,
CONTENT_TYPE_MUSIC,
CONTENT_TYPE_SONIFICATION,
CONTENT_TYPE_SPEECH,
CONTENT_TYPE_UNKNOWN
})
public @interface AudioContentType {} public @interface AudioContentType {}
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE * @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE
@ -270,13 +303,16 @@ public final class C {
android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN; android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
/** /**
* Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}. * Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}. Possible flag value is
* <p> * {@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. * <p>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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) @IntDef(
flag = true,
value = {FLAG_AUDIBILITY_ENFORCED})
public @interface AudioFlags {} public @interface AudioFlags {}
/** /**
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED * @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
@ -284,7 +320,17 @@ public final class C {
public static final int FLAG_AUDIBILITY_ENFORCED = public static final int FLAG_AUDIBILITY_ENFORCED =
android.media.AudioAttributes.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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
USAGE_ALARM, USAGE_ALARM,
@ -376,7 +422,11 @@ public final class C {
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
android.media.AudioAttributes.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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
AUDIOFOCUS_NONE, AUDIOFOCUS_NONE,
@ -400,11 +450,19 @@ public final class C {
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, @IntDef(
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY}) flag = true,
value = {
BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_ENCRYPTED,
BUFFER_FLAG_DECODE_ONLY
})
public @interface BufferFlags {} public @interface BufferFlags {}
/** /**
* Indicates that a buffer holds a synchronization sample. * Indicates that a buffer holds a synchronization sample.
@ -417,10 +475,12 @@ public final class C {
/** Indicates that a buffer is (at least partially) encrypted. */ /** Indicates that a buffer is (at least partially) encrypted. */
public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000 public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000
/** Indicates that a buffer should be decoded but not rendered. */ /** Indicates that a buffer should be decoded but not rendered. */
@SuppressWarnings("NumericOverflow")
public static final int BUFFER_FLAG_DECODE_ONLY = 1 << 31; // 0x80000000 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING})
@ -441,11 +501,13 @@ public final class C {
public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, @IntDef(
SELECTION_FLAG_AUTOSELECT}) flag = true,
value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, SELECTION_FLAG_AUTOSELECT})
public @interface SelectionFlags {} public @interface SelectionFlags {}
/** /**
* Indicates that the track should be selected if user preferences do not state otherwise. * Indicates that the track should be selected if user preferences do not state otherwise.
@ -465,7 +527,8 @@ public final class C {
public static final String LANGUAGE_UNDETERMINED = "und"; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
@ -533,34 +596,22 @@ public final class C {
*/ */
public static final int DATA_TYPE_CUSTOM_BASE = 10000; 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; 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; 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; 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; 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; 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; public static final int TRACK_TYPE_METADATA = 4;
/** /** A type constant for camera motion tracks. */
* A type constant for a dummy or empty track. 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 = 5; public static final int TRACK_TYPE_NONE = 6;
/** /**
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
* equal to this value. * equal to this value.
@ -593,55 +644,42 @@ public final class C {
*/ */
public static final int SELECTION_REASON_CUSTOM_BASE = 10000; 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; 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; 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; 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; 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; public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
/** /** A default size in bytes for a camera motion buffer. */
* A default size in bytes for a muxed buffer (e.g. containing video, audio and text). public static final int DEFAULT_CAMERA_MOTION_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
*/
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 muxed buffer (e.g. containing video, audio and text). */
* "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. 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")
public static final String CENC_TYPE_cenc = "cenc"; 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"; 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"; 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"; public static final String CENC_TYPE_cbcs = "cbcs";
/** /**
@ -733,6 +771,20 @@ public final class C {
*/ */
public static final int MSG_SET_AUX_EFFECT_INFO = 5; 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;
/**
* 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 * 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. * {@link Renderer}s. These custom constants must be greater than or equal to this value.
@ -740,7 +792,9 @@ public final class C {
public static final int MSG_CUSTOM_BASE = 10000; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
@ -770,7 +824,8 @@ public final class C {
public static final int STEREO_MODE_STEREO_MESH = 3; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
@ -789,7 +844,8 @@ public final class C {
public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
@ -808,7 +864,8 @@ public final class C {
public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
@ -836,7 +893,12 @@ public final class C {
*/ */
public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
NETWORK_TYPE_UNKNOWN, NETWORK_TYPE_UNKNOWN,

View file

@ -154,6 +154,7 @@ public class DefaultLoadControl implements LoadControl {
} }
/** Creates a {@link DefaultLoadControl}. */ /** Creates a {@link DefaultLoadControl}. */
@SuppressWarnings("deprecation")
public DefaultLoadControl createDefaultLoadControl() { public DefaultLoadControl createDefaultLoadControl() {
if (allocator == null) { if (allocator == null) {
allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
@ -183,15 +184,15 @@ public class DefaultLoadControl implements LoadControl {
private int targetBufferSize; private int targetBufferSize;
private boolean isBuffering; 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() { public DefaultLoadControl() {
this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE));
} }
/** @deprecated Use {@link Builder} instead. */ /** @deprecated Use {@link Builder} instead. */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public DefaultLoadControl(DefaultAllocator allocator) { public DefaultLoadControl(DefaultAllocator allocator) {
this( this(
allocator, allocator,
@ -205,6 +206,7 @@ public class DefaultLoadControl implements LoadControl {
/** @deprecated Use {@link Builder} instead. */ /** @deprecated Use {@link Builder} instead. */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public DefaultLoadControl( public DefaultLoadControl(
DefaultAllocator allocator, DefaultAllocator allocator,
int minBufferMs, int minBufferMs,

View file

@ -35,6 +35,7 @@ import com.google.android.exoplayer2.text.TextRenderer;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -52,11 +53,11 @@ public class DefaultRenderersFactory implements RenderersFactory {
public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER})
EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {} public @interface ExtensionRendererMode {}
/** /**
* Do not allow use of extension renderers. * Do not allow use of extension renderers.
@ -82,7 +83,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final Context context; private final Context context;
@Nullable private final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager; private final @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
private final @ExtensionRendererMode int extensionRendererMode; private final @ExtensionRendererMode int extensionRendererMode;
private final long allowedVideoJoiningTimeMs; private final long allowedVideoJoiningTimeMs;
@ -98,6 +99,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
* directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. * directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory( public DefaultRenderersFactory(
Context context, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { Context context, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF); this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF);
@ -111,7 +113,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
*/ */
public DefaultRenderersFactory( public DefaultRenderersFactory(
Context context, @ExtensionRendererMode int extensionRendererMode) { 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 +121,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
* DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. * DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory( public DefaultRenderersFactory(
Context context, Context context,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ -138,7 +141,10 @@ public class DefaultRenderersFactory implements RenderersFactory {
Context context, Context context,
@ExtensionRendererMode int extensionRendererMode, @ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) { long allowedVideoJoiningTimeMs) {
this(context, null, extensionRendererMode, allowedVideoJoiningTimeMs); this.context = context;
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
this.drmSessionManager = null;
} }
/** /**
@ -177,6 +183,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
extensionRendererMode, renderersList); extensionRendererMode, renderersList);
buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList); extensionRendererMode, renderersList);
buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList);
return renderersList.toArray(new Renderer[renderersList.size()]); return renderersList.toArray(new Renderer[renderersList.size()]);
} }
@ -355,12 +362,14 @@ public class DefaultRenderersFactory implements RenderersFactory {
* *
* @param context The {@link Context} associated with the player. * @param context The {@link Context} associated with the player.
* @param output An output for the renderers. * @param output An output for the renderers.
* @param outputLooper The looper associated with the thread on which the output should be * @param outputLooper The looper associated with the thread on which the output should be called.
* called.
* @param extensionRendererMode The extension renderer mode. * @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended. * @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, @ExtensionRendererMode int extensionRendererMode,
ArrayList<Renderer> out) { ArrayList<Renderer> out) {
out.add(new TextRenderer(output, outputLooper)); out.add(new TextRenderer(output, outputLooper));
@ -371,16 +380,31 @@ public class DefaultRenderersFactory implements RenderersFactory {
* *
* @param context The {@link Context} associated with the player. * @param context The {@link Context} associated with the player.
* @param output An output for the renderers. * @param output An output for the renderers.
* @param outputLooper The looper associated with the thread on which the output should be * @param outputLooper The looper associated with the thread on which the output should be called.
* called.
* @param extensionRendererMode The extension renderer mode. * @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended. * @param out An array to which the built renderers should be appended.
*/ */
protected void buildMetadataRenderers(Context context, MetadataOutput output, Looper outputLooper, protected void buildMetadataRenderers(
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) { Context context,
MetadataOutput output,
Looper outputLooper,
@ExtensionRendererMode int extensionRendererMode,
ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, outputLooper)); 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<Renderer> out) {
out.add(new CameraMotionRenderer());
}
/** /**
* Builds any miscellaneous renderers used by the player. * Builds any miscellaneous renderers used by the player.
* *

View file

@ -28,7 +28,8 @@ import java.lang.annotation.RetentionPolicy;
public final class ExoPlaybackException extends Exception { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})

View file

@ -227,6 +227,7 @@ public interface ExoPlayer extends Player {
/** @deprecated Use {@link #createMessage(PlayerMessage.Target)} instead. */ /** @deprecated Use {@link #createMessage(PlayerMessage.Target)} instead. */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
void sendMessages(ExoPlayerMessage... messages); void sendMessages(ExoPlayerMessage... messages);
/** /**
@ -234,6 +235,7 @@ public interface ExoPlayer extends Player {
* PlayerMessage#blockUntilDelivered()}. * PlayerMessage#blockUntilDelivered()}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
void blockingSendMessages(ExoPlayerMessage... messages); void blockingSendMessages(ExoPlayerMessage... messages);
/** /**

View file

@ -327,10 +327,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
} else { } else {
long windowPositionUs = positionMs == C.TIME_UNSET long windowPositionUs = positionMs == C.TIME_UNSET
? timeline.getWindow(windowIndex, window).getDefaultPositionUs() : C.msToUs(positionMs); ? timeline.getWindow(windowIndex, window).getDefaultPositionUs() : C.msToUs(positionMs);
Pair<Integer, Long> periodIndexAndPosition = Pair<Object, Long> periodUidAndPosition =
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
maskingWindowPositionMs = C.usToMs(windowPositionUs); maskingWindowPositionMs = C.usToMs(windowPositionUs);
maskingPeriodIndex = periodIndexAndPosition.first; maskingPeriodIndex = timeline.getIndexOfPeriod(periodUidAndPosition.first);
} }
internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs));
for (Player.EventListener listener : listeners) { for (Player.EventListener listener : listeners) {
@ -415,6 +415,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
@Deprecated
@SuppressWarnings("deprecation")
public void sendMessages(ExoPlayerMessage... messages) { public void sendMessages(ExoPlayerMessage... messages) {
for (ExoPlayerMessage message : messages) { for (ExoPlayerMessage message : messages) {
createMessage(message.target).setType(message.messageType).setPayload(message.message).send(); createMessage(message.target).setType(message.messageType).setPayload(message.message).send();
@ -432,6 +434,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
@Deprecated
@SuppressWarnings("deprecation")
public void blockingSendMessages(ExoPlayerMessage... messages) { public void blockingSendMessages(ExoPlayerMessage... messages) {
List<PlayerMessage> playerMessages = new ArrayList<>(); List<PlayerMessage> playerMessages = new ArrayList<>();
for (ExoPlayerMessage message : messages) { for (ExoPlayerMessage message : messages) {
@ -464,7 +468,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (shouldMaskPosition()) { if (shouldMaskPosition()) {
return maskingPeriodIndex; return maskingPeriodIndex;
} else { } else {
return playbackInfo.periodId.periodIndex; return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid);
} }
} }
@ -473,7 +477,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (shouldMaskPosition()) { if (shouldMaskPosition()) {
return maskingWindowIndex; return maskingWindowIndex;
} else { } else {
return playbackInfo.timeline.getPeriod(playbackInfo.periodId.periodIndex, period).windowIndex; return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period)
.windowIndex;
} }
} }
@ -493,18 +498,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public long getDuration() { public long getDuration() {
Timeline timeline = playbackInfo.timeline;
if (timeline.isEmpty()) {
return C.TIME_UNSET;
}
if (isPlayingAd()) { if (isPlayingAd()) {
MediaPeriodId periodId = playbackInfo.periodId; MediaPeriodId periodId = playbackInfo.periodId;
timeline.getPeriod(periodId.periodIndex, period); playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup);
return C.usToMs(adDurationUs); return C.usToMs(adDurationUs);
} else {
return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
} }
return getContentDuration();
} }
@Override @Override
@ -569,10 +569,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; 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 @Override
public long getContentPosition() { public long getContentPosition() {
if (isPlayingAd()) { if (isPlayingAd()) {
playbackInfo.timeline.getPeriod(playbackInfo.periodId.periodIndex, period); playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs); return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs);
} else { } else {
return getCurrentPosition(); return getCurrentPosition();
@ -591,7 +598,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; long contentBufferedPositionUs = playbackInfo.bufferedPositionUs;
if (playbackInfo.loadingMediaPeriodId.isAd()) { if (playbackInfo.loadingMediaPeriodId.isAd()) {
Timeline.Period loadingPeriod = Timeline.Period loadingPeriod =
playbackInfo.timeline.getPeriod(playbackInfo.loadingMediaPeriodId.periodIndex, period); playbackInfo.timeline.getPeriodByUid(playbackInfo.loadingMediaPeriodId.periodUid, period);
contentBufferedPositionUs = contentBufferedPositionUs =
loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex); loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex);
if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) { if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) {
@ -761,7 +768,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) { private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) {
long positionMs = C.usToMs(positionUs); long positionMs = C.usToMs(positionUs);
playbackInfo.timeline.getPeriod(periodId.periodIndex, period); playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
positionMs += period.getPositionInWindowMs(); positionMs += period.getPositionInWindowMs();
return positionMs; return positionMs;
} }

View file

@ -594,7 +594,7 @@ import java.util.Collections;
long periodPositionUs; long periodPositionUs;
long contentPositionUs; long contentPositionUs;
boolean seekPositionAdjusted; boolean seekPositionAdjusted;
Pair<Integer, Long> resolvedSeekPosition = Pair<Object, Long> resolvedSeekPosition =
resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true); resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true);
if (resolvedSeekPosition == null) { if (resolvedSeekPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the // 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; seekPositionAdjusted = true;
} else { } else {
// Update the resolved seek position to take ads into account. // Update the resolved seek position to take ads into account.
int periodIndex = resolvedSeekPosition.first; Object periodUid = resolvedSeekPosition.first;
contentPositionUs = resolvedSeekPosition.second; contentPositionUs = resolvedSeekPosition.second;
periodId = queue.resolveMediaPeriodIdForAds(periodIndex, contentPositionUs); periodId = queue.resolveMediaPeriodIdForAds(periodUid, contentPositionUs);
if (periodId.isAd()) { if (periodId.isAd()) {
periodPositionUs = 0; periodPositionUs = 0;
seekPositionAdjusted = true; seekPositionAdjusted = true;
@ -760,7 +760,7 @@ import java.util.Collections;
int firstPeriodIndex = int firstPeriodIndex =
timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex; .firstPeriodIndex;
return new MediaPeriodId(firstPeriodIndex); return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex));
} }
private void resetInternal( private void resetInternal(
@ -853,16 +853,13 @@ import java.util.Collections;
private void sendMessageToTargetThread(final PlayerMessage message) { private void sendMessageToTargetThread(final PlayerMessage message) {
Handler handler = message.getHandler(); Handler handler = message.getHandler();
handler.post( handler.post(
new Runnable() { () -> {
@Override
public void run() {
try { try {
deliverMessage(message); deliverMessage(message);
} catch (ExoPlaybackException e) { } catch (ExoPlaybackException e) {
Log.e(TAG, "Unexpected error delivering message on external thread.", e); Log.e(TAG, "Unexpected error delivering message on external thread.", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}
}); });
} }
@ -892,7 +889,7 @@ import java.util.Collections;
private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) { private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) {
if (pendingMessageInfo.resolvedPeriodUid == null) { if (pendingMessageInfo.resolvedPeriodUid == null) {
// Position is still unresolved. Try to find window in current timeline. // Position is still unresolved. Try to find window in current timeline.
Pair<Integer, Long> periodPosition = Pair<Object, Long> periodPosition =
resolveSeekPosition( resolveSeekPosition(
new SeekPosition( new SeekPosition(
pendingMessageInfo.message.getTimeline(), pendingMessageInfo.message.getTimeline(),
@ -903,9 +900,9 @@ import java.util.Collections;
return false; return false;
} }
pendingMessageInfo.setResolvedPosition( pendingMessageInfo.setResolvedPosition(
periodPosition.first, playbackInfo.timeline.getIndexOfPeriod(periodPosition.first),
periodPosition.second, periodPosition.second,
playbackInfo.timeline.getUidOfPeriod(periodPosition.first)); periodPosition.first);
} else { } else {
// Position has been resolved for a previous timeline. Try to find the updated period index. // Position has been resolved for a previous timeline. Try to find the updated period index.
int index = playbackInfo.timeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid); int index = playbackInfo.timeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid);
@ -928,7 +925,8 @@ import java.util.Collections;
oldPeriodPositionUs--; oldPeriodPositionUs--;
} }
// Correct next index if necessary (e.g. after seeking, timeline changes, or new messages) // 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 = PendingMessageInfo previousInfo =
nextPendingMessageIndex > 0 ? pendingMessages.get(nextPendingMessageIndex - 1) : null; nextPendingMessageIndex > 0 ? pendingMessages.get(nextPendingMessageIndex - 1) : null;
while (previousInfo != null while (previousInfo != null
@ -1156,7 +1154,7 @@ import java.util.Collections;
playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount); playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount);
pendingPrepareCount = 0; pendingPrepareCount = 0;
if (pendingInitialSeekPosition != null) { if (pendingInitialSeekPosition != null) {
Pair<Integer, Long> periodPosition; Pair<Object, Long> periodPosition;
try { try {
periodPosition = periodPosition =
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
@ -1171,9 +1169,9 @@ import java.util.Collections;
// timeline has changed and a suitable seek position could not be resolved in the new one. // timeline has changed and a suitable seek position could not be resolved in the new one.
handleSourceInfoRefreshEndedPlayback(); handleSourceInfoRefreshEndedPlayback();
} else { } else {
int periodIndex = periodPosition.first; Object periodUid = periodPosition.first;
long positionUs = periodPosition.second; long positionUs = periodPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, positionUs); MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
playbackInfo = playbackInfo =
playbackInfo.fromNewPosition( playbackInfo.fromNewPosition(
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
@ -1182,11 +1180,12 @@ import java.util.Collections;
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(); handleSourceInfoRefreshEndedPlayback();
} else { } else {
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, Pair<Object, Long> defaultPosition =
timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); getPeriodPosition(
int periodIndex = defaultPosition.first; timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET);
Object periodUid = defaultPosition.first;
long startPositionUs = defaultPosition.second; long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, startPositionUs); MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo = playbackInfo =
playbackInfo.fromNewPosition( playbackInfo.fromNewPosition(
periodId, periodId,
@ -1200,12 +1199,12 @@ import java.util.Collections;
if (oldTimeline.isEmpty()) { if (oldTimeline.isEmpty()) {
// If the old timeline is empty, the period queue is also empty. // If the old timeline is empty, the period queue is also empty.
if (!timeline.isEmpty()) { if (!timeline.isEmpty()) {
Pair<Integer, Long> defaultPosition = Pair<Object, Long> defaultPosition =
getPeriodPosition( getPeriodPosition(
timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); timeline, timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET);
int periodIndex = defaultPosition.first; Object periodUid = defaultPosition.first;
long startPositionUs = defaultPosition.second; long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, startPositionUs); MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo = playbackInfo =
playbackInfo.fromNewPosition( playbackInfo.fromNewPosition(
periodId, periodId,
@ -1215,37 +1214,32 @@ import java.util.Collections;
return; return;
} }
MediaPeriodHolder periodHolder = queue.getFrontPeriod(); MediaPeriodHolder periodHolder = queue.getFrontPeriod();
int playingPeriodIndex = playbackInfo.periodId.periodIndex;
long contentPositionUs = playbackInfo.contentPositionUs; long contentPositionUs = playbackInfo.contentPositionUs;
Object playingPeriodUid = Object playingPeriodUid =
periodHolder == null ? oldTimeline.getUidOfPeriod(playingPeriodIndex) : periodHolder.uid; periodHolder == null ? playbackInfo.periodId.periodUid : periodHolder.uid;
int periodIndex = timeline.getIndexOfPeriod(playingPeriodUid); int periodIndex = timeline.getIndexOfPeriod(playingPeriodUid);
if (periodIndex == C.INDEX_UNSET) { if (periodIndex == C.INDEX_UNSET) {
// We didn't find the current period in the new timeline. Attempt to resolve a subsequent // We didn't find the current period in the new timeline. Attempt to resolve a subsequent
// period whose window we can restart from. // period whose window we can restart from.
int newPeriodIndex = resolveSubsequentPeriod(playingPeriodIndex, oldTimeline, timeline); Object newPeriodUid = resolveSubsequentPeriod(playingPeriodUid, oldTimeline, timeline);
if (newPeriodIndex == C.INDEX_UNSET) { if (newPeriodUid == null) {
// We failed to resolve a suitable restart position. // We failed to resolve a suitable restart position.
handleSourceInfoRefreshEndedPlayback(); handleSourceInfoRefreshEndedPlayback();
return; return;
} }
// We resolved a subsequent period. Seek to the default position in the corresponding window. // We resolved a subsequent period. Seek to the default position in the corresponding window.
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, Pair<Object, Long> defaultPosition =
timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); getPeriodPosition(
newPeriodIndex = defaultPosition.first; timeline, timeline.getPeriodByUid(newPeriodUid, period).windowIndex, C.TIME_UNSET);
newPeriodUid = defaultPosition.first;
contentPositionUs = defaultPosition.second; contentPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodIndex, contentPositionUs); MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodUid, contentPositionUs);
if (periodHolder != null) { if (periodHolder != null) {
// Clear the index of each holder that doesn't contain the default position. If a holder // Update the new playing media period info if it already exists.
// 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);
while (periodHolder.next != null) { while (periodHolder.next != null) {
periodHolder = periodHolder.next; periodHolder = periodHolder.next;
if (periodHolder.uid.equals(newPeriodUid)) { if (periodHolder.info.id.equals(periodId)) {
periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info, newPeriodIndex); periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info);
} else {
periodHolder.info = periodHolder.info.copyWithPeriodIndex(C.INDEX_UNSET);
} }
} }
} }
@ -1255,14 +1249,10 @@ import java.util.Collections;
return; return;
} }
// The current period is in the new timeline. Update the playback info.
if (periodIndex != playingPeriodIndex) {
playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex);
}
MediaPeriodId playingPeriodId = playbackInfo.periodId; MediaPeriodId playingPeriodId = playbackInfo.periodId;
if (playingPeriodId.isAd()) { if (playingPeriodId.isAd()) {
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, contentPositionUs); MediaPeriodId periodId =
queue.resolveMediaPeriodIdForAds(playingPeriodUid, contentPositionUs);
if (!periodId.equals(playingPeriodId)) { if (!periodId.equals(playingPeriodId)) {
// The previously playing ad should no longer be played, so skip it. // The previously playing ad should no longer be played, so skip it.
long seekPositionUs = long seekPositionUs =
@ -1287,16 +1277,17 @@ import java.util.Collections;
/** /**
* Given a period index into an old timeline, finds the first subsequent period that also exists * 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 oldTimeline The old timeline.
* @param newTimeline The new timeline. * @param newTimeline The new timeline.
* @return The index in the new timeline of the first subsequent period, or {@link C#INDEX_UNSET} * @return The uid in the new timeline of the first subsequent period, or null if no such period
* if no such period was found. * was found.
*/ */
private int resolveSubsequentPeriod( private @Nullable Object resolveSubsequentPeriod(
int oldPeriodIndex, Timeline oldTimeline, Timeline newTimeline) { Object oldPeriodUid, Timeline oldTimeline, Timeline newTimeline) {
int oldPeriodIndex = oldTimeline.getIndexOfPeriod(oldPeriodUid);
int newPeriodIndex = C.INDEX_UNSET; int newPeriodIndex = C.INDEX_UNSET;
int maxIterations = oldTimeline.getPeriodCount(); int maxIterations = oldTimeline.getPeriodCount();
for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) { for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) {
@ -1308,11 +1299,11 @@ import java.util.Collections;
} }
newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getUidOfPeriod(oldPeriodIndex)); 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. * internal timeline.
* *
* @param seekPosition The position to resolve. * @param seekPosition The position to resolve.
@ -1322,7 +1313,7 @@ import java.util.Collections;
* @throws IllegalSeekPositionException If the window index of the seek position is outside the * @throws IllegalSeekPositionException If the window index of the seek position is outside the
* bounds of the timeline. * bounds of the timeline.
*/ */
private Pair<Integer, Long> resolveSeekPosition( private Pair<Object, Long> resolveSeekPosition(
SeekPosition seekPosition, boolean trySubsequentPeriods) { SeekPosition seekPosition, boolean trySubsequentPeriods) {
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
Timeline seekTimeline = seekPosition.timeline; Timeline seekTimeline = seekPosition.timeline;
@ -1336,7 +1327,7 @@ import java.util.Collections;
seekTimeline = timeline; seekTimeline = timeline;
} }
// Map the SeekPosition to a position in the corresponding timeline. // Map the SeekPosition to a position in the corresponding timeline.
Pair<Integer, Long> periodPosition; Pair<Object, Long> periodPosition;
try { try {
periodPosition = seekTimeline.getPeriodPosition(window, period, seekPosition.windowIndex, periodPosition = seekTimeline.getPeriodPosition(window, period, seekPosition.windowIndex,
seekPosition.windowPositionUs); seekPosition.windowPositionUs);
@ -1350,15 +1341,15 @@ import java.util.Collections;
return periodPosition; return periodPosition;
} }
// Attempt to find the mapped period in the internal timeline. // 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) { if (periodIndex != C.INDEX_UNSET) {
// We successfully located the period in the internal timeline. // We successfully located the period in the internal timeline.
return Pair.create(periodIndex, periodPosition.second); return periodPosition;
} }
if (trySubsequentPeriods) { if (trySubsequentPeriods) {
// Try and find a subsequent period from the seek timeline in the internal timeline. // Try and find a subsequent period from the seek timeline in the internal timeline.
periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); Object periodUid = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline);
if (periodIndex != C.INDEX_UNSET) { if (periodUid != null) {
// We found one. Map the SeekPosition onto the corresponding default position. // We found one. Map the SeekPosition onto the corresponding default position.
return getPeriodPosition( return getPeriodPosition(
timeline, timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); timeline, timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET);
@ -1372,7 +1363,7 @@ import java.util.Collections;
* Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the * Calls {@link Timeline#getPeriodPosition(Timeline.Window, Timeline.Period, int, long)} using the
* current timeline. * current timeline.
*/ */
private Pair<Integer, Long> getPeriodPosition( private Pair<Object, Long> getPeriodPosition(
Timeline timeline, int windowIndex, long windowPositionUs) { Timeline timeline, int windowIndex, long windowPositionUs) {
return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); return timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
} }
@ -1509,14 +1500,12 @@ import java.util.Collections;
if (info == null) { if (info == null) {
mediaSource.maybeThrowSourceInfoRefreshError(); mediaSource.maybeThrowSourceInfoRefreshError();
} else { } else {
Object uid = playbackInfo.timeline.getUidOfPeriod(info.id.periodIndex);
MediaPeriod mediaPeriod = MediaPeriod mediaPeriod =
queue.enqueueNextMediaPeriod( queue.enqueueNextMediaPeriod(
rendererCapabilities, rendererCapabilities,
trackSelector, trackSelector,
loadControl.getAllocator(), loadControl.getAllocator(),
mediaSource, mediaSource,
uid,
info); info);
mediaPeriod.prepare(this, info.startPositionUs); mediaPeriod.prepare(this, info.startPositionUs);
setIsLoading(true); setIsLoading(true);

View file

@ -62,7 +62,6 @@ import com.google.android.exoplayer2.util.Assertions;
* @param trackSelector The track selector. * @param trackSelector The track selector.
* @param allocator The allocator. * @param allocator The allocator.
* @param mediaSource The media source that produced the media period. * @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. * @param info Information used to identify this media period in its timeline period.
*/ */
public MediaPeriodHolder( public MediaPeriodHolder(
@ -71,13 +70,12 @@ import com.google.android.exoplayer2.util.Assertions;
TrackSelector trackSelector, TrackSelector trackSelector,
Allocator allocator, Allocator allocator,
MediaSource mediaSource, MediaSource mediaSource,
Object uid,
MediaPeriodInfo info) { MediaPeriodInfo info) {
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs; this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
this.uid = Assertions.checkNotNull(uid); this.uid = Assertions.checkNotNull(info.id.periodUid);
this.info = info; this.info = info;
sampleStreams = new SampleStream[rendererCapabilities.length]; sampleStreams = new SampleStream[rendererCapabilities.length];
mayRetainStreamFlags = new boolean[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length];

View file

@ -62,20 +62,6 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
this.isFinal = isFinal; 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. */ /** Returns a copy of this instance with the start position set to the specified value. */
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) { public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo( return new MediaPeriodInfo(

View file

@ -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 * 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. * the queue. Also has a reference to the media period currently being read.
*/ */
@SuppressWarnings("UngroupedOverloads")
/* package */ final class MediaPeriodQueue { /* package */ final class MediaPeriodQueue {
/** /**
@ -135,7 +134,6 @@ import com.google.android.exoplayer2.util.Assertions;
* @param trackSelector The track selector. * @param trackSelector The track selector.
* @param allocator The allocator. * @param allocator The allocator.
* @param mediaSource The media source that produced the media period. * @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. * @param info Information used to identify this media period in its timeline period.
*/ */
public MediaPeriod enqueueNextMediaPeriod( public MediaPeriod enqueueNextMediaPeriod(
@ -143,7 +141,6 @@ import com.google.android.exoplayer2.util.Assertions;
TrackSelector trackSelector, TrackSelector trackSelector,
Allocator allocator, Allocator allocator,
MediaSource mediaSource, MediaSource mediaSource,
Object uid,
MediaPeriodInfo info) { MediaPeriodInfo info) {
long rendererPositionOffsetUs = long rendererPositionOffsetUs =
loading == null loading == null
@ -156,7 +153,6 @@ import com.google.android.exoplayer2.util.Assertions;
trackSelector, trackSelector,
allocator, allocator,
mediaSource, mediaSource,
uid,
info); info);
if (loading != null) { if (loading != null) {
Assertions.checkState(hasPlayingPeriod()); 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 // 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 // is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be
// handled here. // 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 // The front period is either playing now, or is being loaded and will become the playing
// period. // period.
MediaPeriodHolder previousPeriodHolder = null; MediaPeriodHolder previousPeriodHolder = null;
MediaPeriodHolder periodHolder = getFrontPeriod(); MediaPeriodHolder periodHolder = getFrontPeriod();
while (periodHolder != null) { while (periodHolder != null) {
if (previousPeriodHolder == null) { if (previousPeriodHolder == null) {
periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info);
} else { } else {
// Check this period holder still follows the previous one, based on the new timeline. // Check this period holder still follows the previous one, based on the new timeline.
if (periodIndex == C.INDEX_UNSET 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. // We've loaded a next media period that is not in the new timeline.
return !removeAfter(previousPeriodHolder); return !removeAfter(previousPeriodHolder);
} }
// Update the period index. // Update the period holder.
periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info);
// Check the media period information matches the new timeline. // Check the media period information matches the new timeline.
if (!canKeepMediaPeriodHolder(periodHolder, periodInfo)) { if (!canKeepMediaPeriodHolder(periodHolder, periodInfo)) {
return !removeAfter(previousPeriodHolder); 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 * 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 info 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.
* @return The updated media period info for the current timeline. * @return The updated media period info for the current timeline.
*/ */
public MediaPeriodInfo getUpdatedMediaPeriodInfo( public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info) {
MediaPeriodInfo mediaPeriodInfo, int newPeriodIndex) { boolean isLastInPeriod = isLastInPeriod(info.id);
return getUpdatedMediaPeriodInfo( boolean isLastInTimeline = isLastInTimeline(info.id, isLastInPeriod);
mediaPeriodInfo, mediaPeriodInfo.id.copyWithPeriodIndex(newPeriodIndex)); 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 * 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. * 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 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. * @return The identifier for the first media period to play, taking into account unplayed ads.
*/ */
public MediaPeriodId resolveMediaPeriodIdForAds(int periodIndex, long positionUs) { public MediaPeriodId resolveMediaPeriodIdForAds(Object periodUid, long positionUs) {
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodIndex); long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodUid);
return resolveMediaPeriodIdForAds(periodIndex, positionUs, windowSequenceNumber); return resolveMediaPeriodIdForAds(periodUid, positionUs, windowSequenceNumber);
} }
// Internal methods. // 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 * 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. * 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 positionUs The next content position in the period to play.
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of * @param windowSequenceNumber The sequence number of the window in the buffered sequence of
* windows this period is part of. * windows this period is part of.
* @return The identifier for the first media period to play, taking into account unplayed ads. * @return The identifier for the first media period to play, taking into account unplayed ads.
*/ */
private MediaPeriodId resolveMediaPeriodIdForAds( private MediaPeriodId resolveMediaPeriodIdForAds(
int periodIndex, long positionUs, long windowSequenceNumber) { Object periodUid, long positionUs, long windowSequenceNumber) {
timeline.getPeriod(periodIndex, period); timeline.getPeriodByUid(periodUid, period);
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
if (adGroupIndex == C.INDEX_UNSET) { if (adGroupIndex == C.INDEX_UNSET) {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs);
@ -397,24 +406,23 @@ import com.google.android.exoplayer2.util.Assertions;
nextAdGroupIndex == C.INDEX_UNSET nextAdGroupIndex == C.INDEX_UNSET
? C.TIME_END_OF_SOURCE ? C.TIME_END_OF_SOURCE
: period.getAdGroupTimeUs(nextAdGroupIndex); : period.getAdGroupTimeUs(nextAdGroupIndex);
return new MediaPeriodId(periodIndex, windowSequenceNumber, endPositionUs); return new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs);
} else { } else {
int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); 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 * Resolves the specified period uid to a corresponding window sequence number. Either by reusing
* reusing the window sequence number of an existing matching media period or by creating a new * the window sequence number of an existing matching media period or by creating a new window
* window sequence number. * 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. * @return A window sequence number for a media period created for this timeline period.
*/ */
private long resolvePeriodIndexToWindowSequenceNumber(int periodIndex) { private long resolvePeriodIndexToWindowSequenceNumber(Object periodUid) {
Object periodUid = timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
int windowIndex = period.windowIndex;
if (oldFrontPeriodUid != null) { if (oldFrontPeriodUid != null) {
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid); int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
if (oldFrontPeriodIndex != C.INDEX_UNSET) { if (oldFrontPeriodIndex != C.INDEX_UNSET) {
@ -469,32 +477,32 @@ import com.google.android.exoplayer2.util.Assertions;
if (lastValidPeriodHolder == null) { if (lastValidPeriodHolder == null) {
return true; return true;
} }
int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid);
while (true) { while (true) {
int nextPeriodIndex = int nextPeriodIndex =
timeline.getNextPeriodIndex( timeline.getNextPeriodIndex(
lastValidPeriodHolder.info.id.periodIndex, currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled);
period,
window,
repeatMode,
shuffleModeEnabled);
while (lastValidPeriodHolder.next != null while (lastValidPeriodHolder.next != null
&& !lastValidPeriodHolder.info.isLastInTimelinePeriod) { && !lastValidPeriodHolder.info.isLastInTimelinePeriod) {
lastValidPeriodHolder = lastValidPeriodHolder.next; lastValidPeriodHolder = lastValidPeriodHolder.next;
} }
if (nextPeriodIndex == C.INDEX_UNSET
|| lastValidPeriodHolder.next == null if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null) {
|| lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) { break;
}
int nextPeriodHolderPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.next.uid);
if (nextPeriodHolderPeriodIndex != nextPeriodIndex) {
break; break;
} }
lastValidPeriodHolder = lastValidPeriodHolder.next; lastValidPeriodHolder = lastValidPeriodHolder.next;
currentPeriodIndex = nextPeriodIndex;
} }
// Release any period holders that don't match the new period order. // Release any period holders that don't match the new period order.
boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder); boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder);
// Update the period info for the last holder, as it may now be the last period in the timeline. // Update the period info for the last holder, as it may now be the last period in the timeline.
lastValidPeriodHolder.info = lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info);
getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info, lastValidPeriodHolder.info.id);
// If renderers may have read from a period that's been removed, it is necessary to restart. // If renderers may have read from a period that's been removed, it is necessary to restart.
return !readingPeriodRemoved || !hasPlayingPeriod(); return !readingPeriodRemoved || !hasPlayingPeriod();
@ -525,9 +533,10 @@ import com.google.android.exoplayer2.util.Assertions;
// timeline is updated, to avoid repeatedly checking the same timeline. // timeline is updated, to avoid repeatedly checking the same timeline.
MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info;
if (mediaPeriodInfo.isLastInTimelinePeriod) { if (mediaPeriodInfo.isLastInTimelinePeriod) {
int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid);
int nextPeriodIndex = int nextPeriodIndex =
timeline.getNextPeriodIndex( timeline.getNextPeriodIndex(
mediaPeriodInfo.id.periodIndex, period, window, repeatMode, shuffleModeEnabled); currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled);
if (nextPeriodIndex == C.INDEX_UNSET) { if (nextPeriodIndex == C.INDEX_UNSET) {
// We can't create a next period yet. // We can't create a next period yet.
return null; return null;
@ -546,7 +555,7 @@ import com.google.android.exoplayer2.util.Assertions;
// the buffer, and start buffering from this point. // the buffer, and start buffering from this point.
long defaultPositionProjectionUs = long defaultPositionProjectionUs =
mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs;
Pair<Integer, Long> defaultPosition = Pair<Object, Long> defaultPosition =
timeline.getPeriodPosition( timeline.getPeriodPosition(
window, window,
period, period,
@ -556,7 +565,7 @@ import com.google.android.exoplayer2.util.Assertions;
if (defaultPosition == null) { if (defaultPosition == null) {
return null; return null;
} }
nextPeriodIndex = defaultPosition.first; nextPeriodUid = defaultPosition.first;
startPositionUs = defaultPosition.second; startPositionUs = defaultPosition.second;
if (mediaPeriodHolder.next != null && mediaPeriodHolder.next.uid.equals(nextPeriodUid)) { if (mediaPeriodHolder.next != null && mediaPeriodHolder.next.uid.equals(nextPeriodUid)) {
windowSequenceNumber = mediaPeriodHolder.next.info.id.windowSequenceNumber; windowSequenceNumber = mediaPeriodHolder.next.info.id.windowSequenceNumber;
@ -567,12 +576,12 @@ import com.google.android.exoplayer2.util.Assertions;
startPositionUs = 0; startPositionUs = 0;
} }
MediaPeriodId periodId = MediaPeriodId periodId =
resolveMediaPeriodIdForAds(nextPeriodIndex, startPositionUs, windowSequenceNumber); resolveMediaPeriodIdForAds(nextPeriodUid, startPositionUs, windowSequenceNumber);
return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs);
} }
MediaPeriodId currentPeriodId = mediaPeriodInfo.id; MediaPeriodId currentPeriodId = mediaPeriodInfo.id;
timeline.getPeriod(currentPeriodId.periodIndex, period); timeline.getPeriodByUid(currentPeriodId.periodUid, period);
if (currentPeriodId.isAd()) { if (currentPeriodId.isAd()) {
int adGroupIndex = currentPeriodId.adGroupIndex; int adGroupIndex = currentPeriodId.adGroupIndex;
int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex); int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex);
@ -586,7 +595,7 @@ import com.google.android.exoplayer2.util.Assertions;
return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup)
? null ? null
: getMediaPeriodInfoForAd( : getMediaPeriodInfoForAd(
currentPeriodId.periodIndex, currentPeriodId.periodUid,
adGroupIndex, adGroupIndex,
nextAdIndexInAdGroup, nextAdIndexInAdGroup,
mediaPeriodInfo.contentPositionUs, mediaPeriodInfo.contentPositionUs,
@ -594,7 +603,7 @@ import com.google.android.exoplayer2.util.Assertions;
} else { } else {
// Play content from the ad group position. // Play content from the ad group position.
return getMediaPeriodInfoForContent( return getMediaPeriodInfoForContent(
currentPeriodId.periodIndex, currentPeriodId.periodUid,
mediaPeriodInfo.contentPositionUs, mediaPeriodInfo.contentPositionUs,
currentPeriodId.windowSequenceNumber); currentPeriodId.windowSequenceNumber);
} }
@ -604,7 +613,7 @@ import com.google.android.exoplayer2.util.Assertions;
if (nextAdGroupIndex == C.INDEX_UNSET) { if (nextAdGroupIndex == C.INDEX_UNSET) {
// The next ad group can't be played. Play content from the ad group position instead. // The next ad group can't be played. Play content from the ad group position instead.
return getMediaPeriodInfoForContent( return getMediaPeriodInfoForContent(
currentPeriodId.periodIndex, currentPeriodId.periodUid,
mediaPeriodInfo.id.endPositionUs, mediaPeriodInfo.id.endPositionUs,
currentPeriodId.windowSequenceNumber); currentPeriodId.windowSequenceNumber);
} }
@ -612,7 +621,7 @@ import com.google.android.exoplayer2.util.Assertions;
return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup)
? null ? null
: getMediaPeriodInfoForAd( : getMediaPeriodInfoForAd(
currentPeriodId.periodIndex, currentPeriodId.periodUid,
nextAdGroupIndex, nextAdGroupIndex,
adIndexInAdGroup, adIndexInAdGroup,
mediaPeriodInfo.id.endPositionUs, mediaPeriodInfo.id.endPositionUs,
@ -634,7 +643,7 @@ import com.google.android.exoplayer2.util.Assertions;
} }
long contentDurationUs = period.getDurationUs(); long contentDurationUs = period.getDurationUs();
return getMediaPeriodInfoForAd( return getMediaPeriodInfoForAd(
currentPeriodId.periodIndex, currentPeriodId.periodUid,
adGroupIndex, adGroupIndex,
adIndexInAdGroup, adIndexInAdGroup,
contentDurationUs, 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( private MediaPeriodInfo getMediaPeriodInfo(
MediaPeriodId id, long contentPositionUs, long startPositionUs) { MediaPeriodId id, long contentPositionUs, long startPositionUs) {
timeline.getPeriod(id.periodIndex, period); timeline.getPeriodByUid(id.periodUid, period);
if (id.isAd()) { if (id.isAd()) {
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
return null; return null;
} }
return getMediaPeriodInfoForAd( return getMediaPeriodInfoForAd(
id.periodIndex, id.periodUid,
id.adGroupIndex, id.adGroupIndex,
id.adIndexInAdGroup, id.adIndexInAdGroup,
contentPositionUs, contentPositionUs,
id.windowSequenceNumber); id.windowSequenceNumber);
} else { } else {
return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, id.windowSequenceNumber); return getMediaPeriodInfoForContent(id.periodUid, startPositionUs, id.windowSequenceNumber);
} }
} }
private MediaPeriodInfo getMediaPeriodInfoForAd( private MediaPeriodInfo getMediaPeriodInfoForAd(
int periodIndex, Object periodUid,
int adGroupIndex, int adGroupIndex,
int adIndexInAdGroup, int adIndexInAdGroup,
long contentPositionUs, long contentPositionUs,
long windowSequenceNumber) { long windowSequenceNumber) {
MediaPeriodId id = MediaPeriodId id =
new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber);
boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInPeriod = isLastInPeriod(id);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
long durationUs = long durationUs =
timeline timeline
.getPeriod(id.periodIndex, period) .getPeriodByUid(id.periodUid, period)
.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup);
long startPositionUs = long startPositionUs =
adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex) adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex)
@ -708,14 +697,14 @@ import com.google.android.exoplayer2.util.Assertions;
} }
private MediaPeriodInfo getMediaPeriodInfoForContent( private MediaPeriodInfo getMediaPeriodInfoForContent(
int periodIndex, long startPositionUs, long windowSequenceNumber) { Object periodUid, long startPositionUs, long windowSequenceNumber) {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
long endPositionUs = long endPositionUs =
nextAdGroupIndex == C.INDEX_UNSET nextAdGroupIndex == C.INDEX_UNSET
? C.TIME_END_OF_SOURCE ? C.TIME_END_OF_SOURCE
: period.getAdGroupTimeUs(nextAdGroupIndex); : period.getAdGroupTimeUs(nextAdGroupIndex);
MediaPeriodId id = new MediaPeriodId(periodIndex, windowSequenceNumber, endPositionUs); MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs);
timeline.getPeriod(id.periodIndex, period); timeline.getPeriodByUid(id.periodUid, period);
boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInPeriod = isLastInPeriod(id);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
long durationUs = long durationUs =
@ -725,7 +714,7 @@ import com.google.android.exoplayer2.util.Assertions;
} }
private boolean isLastInPeriod(MediaPeriodId id) { private boolean isLastInPeriod(MediaPeriodId id) {
int adGroupCount = timeline.getPeriod(id.periodIndex, period).getAdGroupCount(); int adGroupCount = timeline.getPeriodByUid(id.periodUid, period).getAdGroupCount();
if (adGroupCount == 0) { if (adGroupCount == 0) {
return true; return true;
} }
@ -749,9 +738,10 @@ import com.google.android.exoplayer2.util.Assertions;
} }
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { 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 return !timeline.getWindow(windowIndex, window).isDynamic
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, shuffleModeEnabled) && timeline.isLastPeriod(periodIndex, period, window, repeatMode, shuffleModeEnabled)
&& isLastMediaPeriodInPeriod; && isLastMediaPeriodInPeriod;
} }
} }

View file

@ -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 * 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)}. * 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}. */ /** The current {@link Timeline}. */
public final Timeline timeline; public final Timeline timeline;
@ -149,23 +150,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs); 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) { public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
return new PlaybackInfo( return new PlaybackInfo(
timeline, timeline,

View file

@ -29,7 +29,9 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Util; 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.VideoListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -165,12 +167,54 @@ public interface Player {
*/ */
void removeVideoListener(VideoListener listener); void removeVideoListener(VideoListener listener);
/**
* Sets a listener to receive video frame metadata events.
*
* <p>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);
/**
* 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} * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
* currently set on the player. * currently set on the player.
*/ */
void clearVideoSurface(); 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 * 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 * tracking the lifecycle of the surface, and must clear the surface by calling {@code
@ -186,14 +230,6 @@ public interface Player {
*/ */
void setVideoSurface(@Nullable Surface surface); 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 * 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. * rendered. The player will track the lifecycle of the surface automatically.
@ -372,6 +408,7 @@ public interface Player {
abstract class DefaultEventListener implements EventListener { abstract class DefaultEventListener implements EventListener {
@Override @Override
@SuppressWarnings("deprecation")
public void onTimelineChanged( public void onTimelineChanged(
Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
// Call deprecated version. Otherwise, do nothing. // Call deprecated version. Otherwise, do nothing.
@ -405,11 +442,12 @@ public interface Player {
int STATE_ENDED = 4; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
public @interface RepeatMode {} @interface RepeatMode {}
/** /**
* Normal playback without repetition. * Normal playback without repetition.
*/ */
@ -423,7 +461,11 @@ public interface Player {
*/ */
int REPEAT_MODE_ALL = 2; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
DISCONTINUITY_REASON_PERIOD_TRANSITION, DISCONTINUITY_REASON_PERIOD_TRANSITION,
@ -432,7 +474,7 @@ public interface Player {
DISCONTINUITY_REASON_AD_INSERTION, DISCONTINUITY_REASON_AD_INSERTION,
DISCONTINUITY_REASON_INTERNAL DISCONTINUITY_REASON_INTERNAL
}) })
public @interface DiscontinuityReason {} @interface DiscontinuityReason {}
/** /**
* Automatic playback transition from one period in the timeline to the next. The period index may * 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. * be the same as it was before the discontinuity in case the current period is repeated.
@ -451,12 +493,16 @@ public interface Player {
int DISCONTINUITY_REASON_INTERNAL = 4; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({TIMELINE_CHANGE_REASON_PREPARED, TIMELINE_CHANGE_REASON_RESET, @IntDef({
TIMELINE_CHANGE_REASON_DYNAMIC}) TIMELINE_CHANGE_REASON_PREPARED,
public @interface TimelineChangeReason {} TIMELINE_CHANGE_REASON_RESET,
TIMELINE_CHANGE_REASON_DYNAMIC
})
@interface TimelineChangeReason {}
/** /**
* Timeline and manifest changed as a result of a player initialization with new media. * Timeline and manifest changed as a result of a player initialization with new media.
*/ */
@ -719,8 +765,8 @@ public interface Player {
@Nullable Object getCurrentTag(); @Nullable Object getCurrentTag();
/** /**
* Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the * Returns the duration of the current content window or ad in milliseconds, or {@link
* duration is not known. * C#TIME_UNSET} if the duration is not known.
*/ */
long getDuration(); long getDuration();
@ -778,6 +824,13 @@ public interface Player {
*/ */
int getCurrentAdIndexInAdGroup(); 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 * 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 * played once all ads in the ad group have finished playing, in milliseconds. If there is no ad

View file

@ -156,6 +156,14 @@ public final class PlayerMessage {
return handler; 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. * Sets a position in the current window at which the message will be delivered.
* *
@ -170,14 +178,6 @@ public final class PlayerMessage {
return this; 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. * Sets a position in a window at which the message will be delivered.
* *
@ -231,7 +231,7 @@ public final class PlayerMessage {
* Player.EventListener#onPlayerError(ExoPlaybackException)}. * Player.EventListener#onPlayerError(ExoPlaybackException)}.
* *
* @return This message. * @return This message.
* @throws IllegalStateException If {@link #send()} has already been called. * @throws IllegalStateException If this message has already been sent.
*/ */
public PlayerMessage send() { public PlayerMessage send() {
Assertions.checkState(!isSent); Assertions.checkState(!isSent);

View file

@ -34,7 +34,10 @@ import java.lang.annotation.RetentionPolicy;
*/ */
public interface Renderer extends PlayerMessage.Target { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
@interface State {} @interface State {}
@ -202,7 +205,7 @@ public interface Renderer extends PlayerMessage.Target {
* @param operatingRate The operating rate. * @param operatingRate The operating rate.
* @throws ExoPlaybackException If an error occurs handling 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}. * Incrementally renders the {@link SampleStream}.

View file

@ -51,7 +51,9 @@ import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; 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;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -105,39 +107,8 @@ public class SimpleExoPlayer
private float audioVolume; private float audioVolume;
private MediaSource mediaSource; private MediaSource mediaSource;
private List<Cue> currentCues; private List<Cue> currentCues;
private VideoFrameMetadataListener videoFrameMetadataListener;
/** private CameraMotionListener cameraMotionListener;
* @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<FrameworkMediaCrypto> drmSessionManager,
Looper looper) {
this(
/* context= */ null,
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
bandwidthMeter,
new AnalyticsCollector.Factory(),
looper);
}
/** /**
* @param context A {@link Context}. * @param context A {@link Context}.
@ -317,6 +288,13 @@ public class SimpleExoPlayer
setVideoSurface(null); setVideoSurface(null);
} }
@Override
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
@Override @Override
public void setVideoSurface(Surface surface) { public void setVideoSurface(Surface surface) {
removeSurfaceCallbacks(); removeSurfaceCallbacks();
@ -325,13 +303,6 @@ public class SimpleExoPlayer
maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize);
} }
@Override
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
@Override @Override
public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) { public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
removeSurfaceCallbacks(); removeSurfaceCallbacks();
@ -598,6 +569,66 @@ public class SimpleExoPlayer
videoListeners.remove(listener); 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();
}
}
}
@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. * Sets a listener to receive video events, removing all existing listeners.
* *
@ -605,6 +636,7 @@ public class SimpleExoPlayer
* @deprecated Use {@link #addVideoListener(com.google.android.exoplayer2.video.VideoListener)}. * @deprecated Use {@link #addVideoListener(com.google.android.exoplayer2.video.VideoListener)}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public void setVideoListener(VideoListener listener) { public void setVideoListener(VideoListener listener) {
videoListeners.clear(); videoListeners.clear();
if (listener != null) { if (listener != null) {
@ -620,6 +652,7 @@ public class SimpleExoPlayer
* #removeVideoListener(com.google.android.exoplayer2.video.VideoListener)}. * #removeVideoListener(com.google.android.exoplayer2.video.VideoListener)}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public void clearVideoListener(VideoListener listener) { public void clearVideoListener(VideoListener listener) {
removeVideoListener(listener); removeVideoListener(listener);
} }
@ -710,6 +743,7 @@ public class SimpleExoPlayer
* information. * information.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public void setVideoDebugListener(VideoRendererEventListener listener) { public void setVideoDebugListener(VideoRendererEventListener listener) {
videoDebugListeners.retainAll(Collections.singleton(analyticsCollector)); videoDebugListeners.retainAll(Collections.singleton(analyticsCollector));
if (listener != null) { if (listener != null) {
@ -740,6 +774,7 @@ public class SimpleExoPlayer
* information. * information.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public void setAudioDebugListener(AudioRendererEventListener listener) { public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListeners.retainAll(Collections.singleton(analyticsCollector)); audioDebugListeners.retainAll(Collections.singleton(analyticsCollector));
if (listener != null) { if (listener != null) {
@ -940,6 +975,8 @@ public class SimpleExoPlayer
} }
@Override @Override
@Deprecated
@SuppressWarnings("deprecation")
public void sendMessages(ExoPlayerMessage... messages) { public void sendMessages(ExoPlayerMessage... messages) {
player.sendMessages(messages); player.sendMessages(messages);
} }
@ -950,6 +987,8 @@ public class SimpleExoPlayer
} }
@Override @Override
@Deprecated
@SuppressWarnings("deprecation")
public void blockingSendMessages(ExoPlayerMessage... messages) { public void blockingSendMessages(ExoPlayerMessage... messages) {
player.blockingSendMessages(messages); player.blockingSendMessages(messages);
} }
@ -1054,6 +1093,11 @@ public class SimpleExoPlayer
return player.getCurrentAdIndexInAdGroup(); return player.getCurrentAdIndexInAdGroup();
} }
@Override
public long getContentDuration() {
return player.getContentDuration();
}
@Override @Override
public long getContentPosition() { public long getContentPosition() {
return player.getContentPosition(); return player.getContentPosition();

View file

@ -702,13 +702,13 @@ public abstract class Timeline {
* Calls {@link #getPeriodPosition(Window, Period, int, long, long)} with a zero default position * Calls {@link #getPeriodPosition(Window, Period, int, long, long)} with a zero default position
* projection. * projection.
*/ */
public final Pair<Integer, Long> getPeriodPosition(Window window, Period period, int windowIndex, public final Pair<Object, Long> getPeriodPosition(
long windowPositionUs) { Window window, Period period, int windowIndex, long windowPositionUs) {
return getPeriodPosition(window, period, windowIndex, windowPositionUs, 0); 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 window A {@link Window} that may be overwritten.
* @param period A {@link Period} that may be overwritten. * @param period A {@link Period} that may be overwritten.
@ -717,12 +717,16 @@ public abstract class Timeline {
* start position. * start position.
* @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
* duration into the future by which the window's position should be projected. * 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 * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
* position could not be projected by {@code defaultPositionProjectionUs}. * position could not be projected by {@code defaultPositionProjectionUs}.
*/ */
public final Pair<Integer, Long> getPeriodPosition(Window window, Period period, int windowIndex, public final Pair<Object, Long> getPeriodPosition(
long windowPositionUs, long defaultPositionProjectionUs) { Window window,
Period period,
int windowIndex,
long windowPositionUs,
long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, getWindowCount()); Assertions.checkIndex(windowIndex, 0, getWindowCount());
getWindow(windowIndex, window, false, defaultPositionProjectionUs); getWindow(windowIndex, window, false, defaultPositionProjectionUs);
if (windowPositionUs == C.TIME_UNSET) { if (windowPositionUs == C.TIME_UNSET) {
@ -733,13 +737,13 @@ public abstract class Timeline {
} }
int periodIndex = window.firstPeriodIndex; int periodIndex = window.firstPeriodIndex;
long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; 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 while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
&& periodIndex < window.lastPeriodIndex) { && periodIndex < window.lastPeriodIndex) {
periodPositionUs -= periodDurationUs; 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);
} }
/** /**

View file

@ -304,6 +304,11 @@ public class AnalyticsCollector
// VideoListener implementation. // VideoListener implementation.
@Override
public final void onRenderedFirstFrame() {
// Do nothing. Already reported in VideoRendererEventListener.onRenderedFirstFrame.
}
@Override @Override
public final void onVideoSizeChanged( public final void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { 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. // MediaSourceEventListener implementation.
@Override @Override
@ -705,11 +705,10 @@ public class AnalyticsCollector
public @Nullable MediaPeriodId tryResolveWindowIndex(int windowIndex) { public @Nullable MediaPeriodId tryResolveWindowIndex(int windowIndex) {
MediaPeriodId match = null; MediaPeriodId match = null;
if (timeline != null) { if (timeline != null) {
int timelinePeriodCount = timeline.getPeriodCount();
for (int i = 0; i < activeMediaPeriods.size(); i++) { for (int i = 0; i < activeMediaPeriods.size(); i++) {
WindowAndMediaPeriodId mediaPeriod = activeMediaPeriods.get(i); WindowAndMediaPeriodId mediaPeriod = activeMediaPeriods.get(i);
int periodIndex = mediaPeriod.mediaPeriodId.periodIndex; int periodIndex = timeline.getIndexOfPeriod(mediaPeriod.mediaPeriodId.periodUid);
if (periodIndex < timelinePeriodCount if (periodIndex != C.INDEX_UNSET
&& timeline.getPeriod(periodIndex, period).windowIndex == windowIndex) { && timeline.getPeriod(periodIndex, period).windowIndex == windowIndex) {
if (match != null) { if (match != null) {
// Ambiguous match. // Ambiguous match.
@ -731,10 +730,10 @@ public class AnalyticsCollector
public void onTimelineChanged(Timeline timeline) { public void onTimelineChanged(Timeline timeline) {
for (int i = 0; i < activeMediaPeriods.size(); i++) { for (int i = 0; i < activeMediaPeriods.size(); i++) {
activeMediaPeriods.set( activeMediaPeriods.set(
i, updateMediaPeriodToNewTimeline(activeMediaPeriods.get(i), timeline)); i, updateWindowIndexToNewTimeline(activeMediaPeriods.get(i), timeline));
} }
if (readingMediaPeriod != null) { if (readingMediaPeriod != null) {
readingMediaPeriod = updateMediaPeriodToNewTimeline(readingMediaPeriod, timeline); readingMediaPeriod = updateWindowIndexToNewTimeline(readingMediaPeriod, timeline);
} }
this.timeline = timeline; this.timeline = timeline;
updateLastReportedPlayingMediaPeriod(); updateLastReportedPlayingMediaPeriod();
@ -779,19 +778,17 @@ public class AnalyticsCollector
} }
} }
private WindowAndMediaPeriodId updateMediaPeriodToNewTimeline( private WindowAndMediaPeriodId updateWindowIndexToNewTimeline(
WindowAndMediaPeriodId mediaPeriod, Timeline newTimeline) { WindowAndMediaPeriodId mediaPeriod, Timeline newTimeline) {
if (newTimeline.isEmpty() || timeline.isEmpty()) { if (newTimeline.isEmpty() || timeline.isEmpty()) {
return mediaPeriod; return mediaPeriod;
} }
Object uid = timeline.getUidOfPeriod(mediaPeriod.mediaPeriodId.periodIndex); int newPeriodIndex = newTimeline.getIndexOfPeriod(mediaPeriod.mediaPeriodId.periodUid);
int newPeriodIndex = newTimeline.getIndexOfPeriod(uid);
if (newPeriodIndex == C.INDEX_UNSET) { if (newPeriodIndex == C.INDEX_UNSET) {
return mediaPeriod; return mediaPeriod;
} }
int newWindowIndex = newTimeline.getPeriod(newPeriodIndex, period).windowIndex; int newWindowIndex = newTimeline.getPeriod(newPeriodIndex, period).windowIndex;
return new WindowAndMediaPeriodId( return new WindowAndMediaPeriodId(newWindowIndex, mediaPeriod.mediaPeriodId);
newWindowIndex, mediaPeriod.mediaPeriodId.copyWithPeriodIndex(newPeriodIndex));
} }
} }

View file

@ -33,7 +33,10 @@ public final class Ac3Util {
/** Holds sample format information as presented by a syncframe header. */ /** Holds sample format information as presented by a syncframe header. */
public static final class SyncFrameInfo { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2}) @IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2})
public @interface StreamType {} public @interface StreamType {}

View file

@ -52,7 +52,10 @@ public final class AudioFocusManager {
void executePlayerCommand(@PlayerCommand int playerCommand); 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
PLAYER_COMMAND_DO_NOT_PLAY, PLAYER_COMMAND_DO_NOT_PLAY,
@ -134,8 +137,7 @@ public final class AudioFocusManager {
* managed automatically. * managed automatically.
* @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}.
* @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}. * @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 * @return A {@link PlayerCommand} to execute on the player.
* #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}.
*/ */
public @PlayerCommand int setAudioAttributes( public @PlayerCommand int setAudioAttributes(
@Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { @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)}. * Called by a player as part of {@link ExoPlayer#prepare(MediaSource, boolean, boolean)}.
* *
* @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. * @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 * @return A {@link PlayerCommand} to execute on the player.
* #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}.
*/ */
public @PlayerCommand int handlePrepare(boolean playWhenReady) { public @PlayerCommand int handlePrepare(boolean playWhenReady) {
if (audioManager == null) { if (audioManager == null) {
@ -185,8 +186,7 @@ public final class AudioFocusManager {
* *
* @param playWhenReady The desired value of playWhenReady. * @param playWhenReady The desired value of playWhenReady.
* @param playerState The current state of the player. * @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 * @return A {@link PlayerCommand} to execute on the player.
* #PLAYER_COMMAND_WAIT_FOR_CALLBACK}, and {@link #PLAYER_COMMAND_PLAY_WHEN_READY}.
*/ */
public @PlayerCommand int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { public @PlayerCommand int handleSetPlayWhenReady(boolean playWhenReady, int playerState) {
if (audioManager == null) { if (audioManager == null) {

View file

@ -104,12 +104,7 @@ public interface AudioRendererEventListener {
*/ */
public void enabled(final DecoderCounters decoderCounters) { public void enabled(final DecoderCounters decoderCounters) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(() -> listener.onAudioEnabled(decoderCounters));
@Override
public void run() {
listener.onAudioEnabled(decoderCounters);
}
});
} }
} }
@ -119,13 +114,10 @@ public interface AudioRendererEventListener {
public void decoderInitialized(final String decoderName, public void decoderInitialized(final String decoderName,
final long initializedTimestampMs, final long initializationDurationMs) { final long initializedTimestampMs, final long initializationDurationMs) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(
@Override () ->
public void run() { listener.onAudioDecoderInitialized(
listener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, decoderName, initializedTimestampMs, initializationDurationMs));
initializationDurationMs);
}
});
} }
} }
@ -134,12 +126,7 @@ public interface AudioRendererEventListener {
*/ */
public void inputFormatChanged(final Format format) { public void inputFormatChanged(final Format format) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(() -> listener.onAudioInputFormatChanged(format));
@Override
public void run() {
listener.onAudioInputFormatChanged(format);
}
});
} }
} }
@ -149,12 +136,8 @@ public interface AudioRendererEventListener {
public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs, public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) { final long elapsedSinceLastFeedMs) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(
@Override () -> listener.onAudioSinkUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs));
public void run() {
listener.onAudioSinkUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
} }
} }
@ -163,12 +146,10 @@ public interface AudioRendererEventListener {
*/ */
public void disabled(final DecoderCounters counters) { public void disabled(final DecoderCounters counters) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(
@Override () -> {
public void run() {
counters.ensureUpdated(); counters.ensureUpdated();
listener.onAudioDisabled(counters); listener.onAudioDisabled(counters);
}
}); });
} }
} }
@ -178,12 +159,7 @@ public interface AudioRendererEventListener {
*/ */
public void audioSessionId(final int audioSessionId) { public void audioSessionId(final int audioSessionId) {
if (listener != null) { if (listener != null) {
handler.post(new Runnable() { handler.post(() -> listener.onAudioSessionId(audioSessionId));
@Override
public void run() {
listener.onAudioSessionId(audioSessionId);
}
});
} }
} }

View file

@ -547,9 +547,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, protected boolean processOutputBuffer(
ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, long positionUs,
boolean shouldSkip) throws ExoPlaybackException { 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) { if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// Discard output buffers from the passthrough (raw) decoder containing codec specific data. // Discard output buffers from the passthrough (raw) decoder containing codec specific data.
codec.releaseOutputBuffer(bufferIndex, false); codec.releaseOutputBuffer(bufferIndex, false);

View file

@ -98,6 +98,8 @@ import java.nio.ByteOrder;
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
@ -134,6 +136,8 @@ import java.nio.ByteOrder;
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:

View file

@ -27,11 +27,16 @@ import java.nio.ByteBuffer;
public class DecoderInputBuffer extends Buffer { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, @IntDef({
BUFFER_REPLACEMENT_MODE_DIRECT}) BUFFER_REPLACEMENT_MODE_DISABLED,
BUFFER_REPLACEMENT_MODE_NORMAL,
BUFFER_REPLACEMENT_MODE_DIRECT
})
public @interface BufferReplacementMode {} public @interface BufferReplacementMode {}
/** /**
* Disallows buffer replacement. * Disallows buffer replacement.
@ -85,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 * Ensures that {@link #data} is large enough to accommodate a write of a given length at its
* current position. * current position.
* <p> *
* If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is * <p>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} * 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. * whose capacity is sufficient. Data up to the current position is copied to the new buffer.
* *
@ -94,7 +99,7 @@ public class DecoderInputBuffer extends Buffer {
* @throws IllegalStateException If there is insufficient capacity to accommodate the write and * @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}. * 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) { if (data == null) {
data = createReplacementByteBuffer(length); data = createReplacementByteBuffer(length);
return; return;

View file

@ -20,11 +20,11 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayDeque; import java.util.ArrayDeque;
/** /** Base class for {@link Decoder}s that use their own decode thread. */
* Base class for {@link Decoder}s that use their own decode thread. @SuppressWarnings("UngroupedOverloads")
*/ public abstract class SimpleDecoder<
public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer, I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception>
E extends Exception> implements Decoder<I, O, E> { implements Decoder<I, O, E> {
private final Thread decodeThread; private final Thread decodeThread;

View file

@ -67,7 +67,10 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
*/ */
public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE})
public @interface Mode {} public @interface Mode {}

View file

@ -40,11 +40,12 @@ public interface DrmSession<T extends ExoMediaCrypto> {
} }
/** /**
* 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
public @interface State {} @interface State {}
/** /**
* The session has been released. * The session has been released.
*/ */

View file

@ -24,8 +24,6 @@ import android.media.MediaDrm;
import android.media.MediaDrmException; import android.media.MediaDrmException;
import android.media.NotProvisionedException; import android.media.NotProvisionedException;
import android.media.UnsupportedSchemeException; 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.C;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -68,10 +66,10 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
private FrameworkMediaDrm(UUID uuid) throws UnsupportedSchemeException { private FrameworkMediaDrm(UUID uuid) throws UnsupportedSchemeException {
Assertions.checkNotNull(uuid); Assertions.checkNotNull(uuid);
Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead"); Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead");
// ClearKey had to be accessed using the Common PSSH UUID prior to API level 27.
uuid = Util.SDK_INT < 27 && C.CLEARKEY_UUID.equals(uuid) ? C.COMMON_PSSH_UUID : uuid;
this.uuid = uuid; this.uuid = uuid;
this.mediaDrm = new MediaDrm(uuid); // ClearKey had to be accessed using the Common PSSH UUID prior to API level 27.
this.mediaDrm =
new MediaDrm(Util.SDK_INT < 27 && C.CLEARKEY_UUID.equals(uuid) ? C.COMMON_PSSH_UUID : uuid);
if (C.WIDEVINE_UUID.equals(uuid) && needsForceWidevineL3Workaround()) { if (C.WIDEVINE_UUID.equals(uuid) && needsForceWidevineL3Workaround()) {
forceWidevineL3(mediaDrm); forceWidevineL3(mediaDrm);
} }
@ -80,13 +78,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
@Override @Override
public void setOnEventListener( public void setOnEventListener(
final ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto> listener) { final ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto> listener) {
mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() { mediaDrm.setOnEventListener(
@Override listener == null
public void onEvent(@NonNull MediaDrm md, @Nullable byte[] sessionId, int event, int extra, ? null
byte[] data) { : (mediaDrm, sessionId, event, extra, data) ->
listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data); listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data));
}
});
} }
@Override @Override
@ -99,20 +95,13 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
mediaDrm.setOnKeyStatusChangeListener( mediaDrm.setOnKeyStatusChangeListener(
listener == null listener == null
? null ? null
: new MediaDrm.OnKeyStatusChangeListener() { : (mediaDrm, sessionId, keyInfo, hasNewUsableKey) -> {
@Override
public void onKeyStatusChange(
@NonNull MediaDrm md,
@NonNull byte[] sessionId,
@NonNull List<MediaDrm.KeyStatus> keyInfo,
boolean hasNewUsableKey) {
List<KeyStatus> exoKeyInfo = new ArrayList<>(); List<KeyStatus> exoKeyInfo = new ArrayList<>();
for (MediaDrm.KeyStatus keyStatus : keyInfo) { for (MediaDrm.KeyStatus keyStatus : keyInfo) {
exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId())); exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId()));
} }
listener.onKeyStatusChange( listener.onKeyStatusChange(
FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey); FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey);
}
}, },
null); null);
} }
@ -238,7 +227,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
} }
@SuppressLint("WrongConstant") // Suppress spurious lint error [Internal ref: b/32137960] @SuppressLint("WrongConstant") // Suppress spurious lint error [Internal ref: b/32137960]
private static final void forceWidevineL3(MediaDrm mediaDrm) { private static void forceWidevineL3(MediaDrm mediaDrm) {
mediaDrm.setPropertyString("securityLevel", "L3"); mediaDrm.setPropertyString("securityLevel", "L3");
} }

View file

@ -25,7 +25,8 @@ import java.lang.annotation.RetentionPolicy;
public final class UnsupportedDrmException extends Exception { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})

View file

@ -95,9 +95,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
* *
* @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate * @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate
* assumption should be enabled for all extractors that support it. * 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; this.constantBitrateSeekingEnabled = constantBitrateSeekingEnabled;
return this;
} }
/** /**

View file

@ -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.
}
}

View file

@ -44,7 +44,10 @@ public interface Extractor {
*/ */
int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT}) @IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT})
@interface ReadResult {} @interface ReadResult {}

View file

@ -29,16 +29,13 @@ import java.util.regex.Pattern;
public final class GaplessInfoHolder { public final class GaplessInfoHolder {
/** /**
* A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed to
* to {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback * {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback information
* information are decoded. * are decoded.
*/ */
public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = new FramePredicate() { public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE =
@Override (majorVersion, id0, id1, id2, id3) ->
public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) { id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2);
return id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2);
}
};
private static final String GAPLESS_DOMAIN = "com.apple.iTunes"; private static final String GAPLESS_DOMAIN = "com.apple.iTunes";
private static final String GAPLESS_DESCRIPTION = "iTunSMPB"; private static final String GAPLESS_DESCRIPTION = "iTunSMPB";

View file

@ -153,7 +153,9 @@ public final class MpegAudioHeader {
} }
int padding = (headerData >>> 9) & 1; int padding = (headerData >>> 9) & 1;
int bitrate, frameSize, samplesPerFrame; int bitrate;
int frameSize;
int samplesPerFrame;
if (layer == 3) { if (layer == 3) {
// Layer I (layer == 3) // Layer I (layer == 3)
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];

View file

@ -47,7 +47,10 @@ public final class AmrExtractor implements Extractor {
/** Factory for {@link AmrExtractor} instances. */ /** Factory for {@link AmrExtractor} instances. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AmrExtractor()}; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,

View file

@ -27,7 +27,10 @@ import java.lang.annotation.RetentionPolicy;
*/ */
/* package */ interface EbmlReaderOutput { /* 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT}) @IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT})
@interface ElementType {} @interface ElementType {}

View file

@ -65,10 +65,13 @@ public final class MatroskaExtractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new MatroskaExtractor()}; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES}) @IntDef(
flag = true,
value = {FLAG_DISABLE_SEEK_FOR_CUES})
public @interface Flags {} public @interface Flags {}
/** /**
* Flag to disable seeking for cues. * Flag to disable seeking for cues.

View file

@ -47,10 +47,13 @@ public final class Mp3Extractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp3Extractor()}; 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) @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 {} public @interface Flags {}
/** /**
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would * Flag to force enable seeking using a constant bitrate assumption in cases where seeking would

View file

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@SuppressWarnings("ConstantField")
/* package*/ abstract class Atom { /* package*/ abstract class Atom {
/** /**

View file

@ -38,9 +38,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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 { /* package */ final class AtomParsers {
/** Thrown if an edit list couldn't be applied. */ /** Thrown if an edit list couldn't be applied. */
@ -619,9 +618,11 @@ import java.util.List;
long timescale = mdhd.readUnsignedInt(); long timescale = mdhd.readUnsignedInt();
mdhd.skipBytes(version == 0 ? 4 : 8); mdhd.skipBytes(version == 0 ? 4 : 8);
int languageCode = mdhd.readUnsignedShort(); int languageCode = mdhd.readUnsignedShort();
String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) String language =
""
+ (char) (((languageCode >> 10) & 0x1F) + 0x60)
+ (char) (((languageCode >> 5) & 0x1F) + 0x60) + (char) (((languageCode >> 5) & 0x1F) + 0x60)
+ (char) (((languageCode) & 0x1F) + 0x60); + (char) ((languageCode & 0x1F) + 0x60);
return Pair.create(timescale, language); return Pair.create(timescale, language);
} }

View file

@ -62,12 +62,21 @@ public final class FragmentedMp4Extractor implements Extractor {
() -> new Extractor[] {new FragmentedMp4Extractor()}; () -> 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, @IntDef(
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_SIDELOADED, flag = true,
FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) 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 {} public @interface Flags {}
/** /**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
@ -93,7 +102,10 @@ public final class FragmentedMp4Extractor implements Extractor {
public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1 << 4; // 16 public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1 << 4; // 16
private static final String TAG = "FragmentedMp4Extractor"; private static final String TAG = "FragmentedMp4Extractor";
@SuppressWarnings("ConstantField")
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = 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}; new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
private static final Format EMSG_FORMAT = private static final Format EMSG_FORMAT =

View file

@ -50,10 +50,13 @@ public final class Mp4Extractor implements Extractor, SeekMap {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp4Extractor()}; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) @IntDef(
flag = true,
value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS})
public @interface Flags {} public @interface Flags {}
/** /**
* Flag to ignore any edit lists in the stream. * Flag to ignore any edit lists in the stream.

View file

@ -28,7 +28,8 @@ import java.lang.annotation.RetentionPolicy;
public final class Track { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})

View file

@ -26,9 +26,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
/** /** StreamReader abstract class. */
* StreamReader abstract class. @SuppressWarnings("UngroupedOverloads")
*/
/* package */ abstract class StreamReader { /* package */ abstract class StreamReader {
private static final int STATE_READ_HEADERS = 0; private static final int STATE_READ_HEADERS = 0;

View file

@ -153,7 +153,7 @@ import java.util.ArrayList;
buffer.setLimit(buffer.limit() + 4); buffer.setLimit(buffer.limit() + 4);
// The vorbis decoder expects the number of samples in the packet // The vorbis decoder expects the number of samples in the packet
// to be appended to the audio data as an int32 // 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() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF);
buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF);
buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF);

View file

@ -43,7 +43,10 @@ public final class AdtsExtractor implements Extractor {
/** Factory for {@link AdtsExtractor} instances. */ /** Factory for {@link AdtsExtractor} instances. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AdtsExtractor()}; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,

View file

@ -81,7 +81,6 @@ public final class AdtsReader implements ElementaryStreamReader {
private int firstFrameSampleRateIndex; private int firstFrameSampleRateIndex;
private int currentFrameVersion; private int currentFrameVersion;
private int currentFrameSampleRateIndex;
// Used when parsing the header. // Used when parsing the header.
private boolean hasOutputFormat; private boolean hasOutputFormat;
@ -170,6 +169,8 @@ public final class AdtsReader implements ElementaryStreamReader {
case STATE_READING_SAMPLE: case STATE_READING_SAMPLE:
readSample(data); readSample(data);
break; break;
default:
throw new IllegalStateException();
} }
} }
} }
@ -327,7 +328,7 @@ public final class AdtsReader implements ElementaryStreamReader {
adtsScratch.data[0] = buffer.data[buffer.getPosition()]; adtsScratch.data[0] = buffer.data[buffer.getPosition()];
adtsScratch.setPosition(2); adtsScratch.setPosition(2);
currentFrameSampleRateIndex = adtsScratch.readBits(4); int currentFrameSampleRateIndex = adtsScratch.readBits(4);
if (firstFrameSampleRateIndex != C.INDEX_UNSET if (firstFrameSampleRateIndex != C.INDEX_UNSET
&& currentFrameSampleRateIndex != firstFrameSampleRateIndex) { && currentFrameSampleRateIndex != firstFrameSampleRateIndex) {
// Invalid header. // Invalid header.

View file

@ -34,13 +34,24 @@ import java.util.List;
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { 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) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, @IntDef(
FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM, flag = true,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS}) 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 @interface Flags {}
public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; 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_AAC_STREAM = 1 << 1;
public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;

View file

@ -111,6 +111,8 @@ public final class DtsReader implements ElementaryStreamReader {
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
} }
break; break;
default:
throw new IllegalStateException();
} }
} }
} }

View file

@ -134,6 +134,8 @@ public final class LatmReader implements ElementaryStreamReader {
state = STATE_FINDING_SYNC_1; state = STATE_FINDING_SYNC_1;
} }
break; break;
default:
throw new IllegalStateException();
} }
} }
} }
@ -250,6 +252,8 @@ public final class LatmReader implements ElementaryStreamReader {
case 7: case 7:
data.skipBits(1); // HVXCframeLengthTableIndex. data.skipBits(1); // HVXCframeLengthTableIndex.
break; break;
default:
throw new IllegalStateException();
} }
} }

View file

@ -100,6 +100,8 @@ public final class MpegAudioReader implements ElementaryStreamReader {
case STATE_READING_FRAME: case STATE_READING_FRAME:
readFrameRemainder(data); readFrameRemainder(data);
break; break;
default:
throw new IllegalStateException();
} }
} }
} }

View file

@ -100,6 +100,8 @@ public final class PesReader implements TsPayloadReader {
// Either way, notify the reader that it has now finished. // Either way, notify the reader that it has now finished.
reader.packetFinished(); reader.packetFinished();
break; break;
default:
throw new IllegalStateException();
} }
setState(STATE_READING_HEADER); setState(STATE_READING_HEADER);
} }
@ -140,6 +142,8 @@ public final class PesReader implements TsPayloadReader {
} }
} }
break; break;
default:
throw new IllegalStateException();
} }
} }
} }

View file

@ -54,7 +54,8 @@ public final class TsExtractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new TsExtractor()}; 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS})
@ -243,8 +244,8 @@ public final class TsExtractor implements Extractor {
@Override @Override
public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition) public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (tracksEnded) {
long inputLength = input.getLength(); long inputLength = input.getLength();
if (tracksEnded) {
boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS; boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS;
if (canReadDuration && !durationReader.isDurationReadFinished()) { if (canReadDuration && !durationReader.isDurationReadFinished()) {
return durationReader.readDuration(input, seekPosition, pcrPid); return durationReader.readDuration(input, seekPosition, pcrPid);
@ -324,10 +325,10 @@ public final class TsExtractor implements Extractor {
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
tsPacketBuffer.setLimit(limit); tsPacketBuffer.setLimit(limit);
} }
if (mode != MODE_HLS && !wereTracksEnded && tracksEnded) { if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) {
// We have read all tracks from all PMTs in this stream. Now seek to the beginning and read // We have read all tracks from all PMTs in this non-live stream. Now seek to the beginning
// again to make sure we output all media, including any contained in packets prior to those // and read again to make sure we output all media, including any contained in packets prior
// containing the track information. // to those containing the track information.
pendingSeekToStart = true; pendingSeekToStart = true;
} }

View file

@ -43,6 +43,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.NalUnitUtil; 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.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -272,10 +273,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final DecoderInputBuffer buffer; private final DecoderInputBuffer buffer;
private final DecoderInputBuffer flagsOnlyBuffer; private final DecoderInputBuffer flagsOnlyBuffer;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final TimedValueQueue<Format> formatQueue;
private final List<Long> decodeOnlyPresentationTimestamps; private final List<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private Format format; private Format format;
private Format pendingFormat;
private Format outputFormat;
private DrmSession<FrameworkMediaCrypto> drmSession; private DrmSession<FrameworkMediaCrypto> drmSession;
private DrmSession<FrameworkMediaCrypto> pendingDrmSession; private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private MediaCodec codec; private MediaCodec codec;
@ -288,12 +292,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode;
private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround; private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround; private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround; private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
private boolean codecNeedsEosPropagation;
private ByteBuffer[] inputBuffers; private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers; private ByteBuffer[] outputBuffers;
private long codecHotswapDeadlineMs; private long codecHotswapDeadlineMs;
@ -344,6 +348,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
formatQueue = new TimedValueQueue<>();
decodeOnlyPresentationTimestamps = new ArrayList<>(); decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo(); outputBufferInfo = new MediaCodec.BufferInfo();
codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReconfigurationState = RECONFIGURATION_STATE_NONE;
@ -463,10 +468,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecInfo);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
codecNeedsEosPropagation =
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
codecHotswapDeadlineMs = codecHotswapDeadlineMs =
getState() == STATE_STARTED getState() == STATE_STARTED
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS)
@ -481,11 +487,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true; 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() { protected final MediaCodec getCodec() {
return codec; return codec;
} }
protected final MediaCodecInfo getCodecInfo() { protected final @Nullable MediaCodecInfo getCodecInfo() {
return codecInfo; return codecInfo;
} }
@ -501,6 +515,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codec != null) { if (codec != null) {
flushCodec(); flushCodec();
} }
formatQueue.clear();
} }
@Override @Override
@ -547,11 +562,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsDiscardToSpsWorkaround = false; codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false; codecNeedsFlushWorkaround = false;
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsEosPropagationWorkaround = false;
codecNeedsEosFlushWorkaround = false; codecNeedsEosFlushWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false; codecNeedsMonoChannelCountWorkaround = false;
codecNeedsAdaptationWorkaroundBuffer = false; codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false;
codecNeedsEosPropagation = false;
codecReceivedEos = false; codecReceivedEos = false;
codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE;
@ -849,7 +864,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { 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 // 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. // that it outputs any remaining buffers before we release it.
if (codecNeedsEosPropagationWorkaround) { if (codecNeedsEosPropagation) {
// Do nothing. // Do nothing.
} else { } else {
codecReceivedEos = true; codecReceivedEos = true;
@ -917,7 +932,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return false; return false;
} }
try { try {
if (codecNeedsEosPropagationWorkaround) { if (codecNeedsEosPropagation) {
// Do nothing. // Do nothing.
} else { } else {
codecReceivedEos = true; codecReceivedEos = true;
@ -956,6 +971,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (buffer.isDecodeOnly()) { if (buffer.isDecodeOnly()) {
decodeOnlyPresentationTimestamps.add(presentationTimeUs); decodeOnlyPresentationTimestamps.add(presentationTimeUs);
} }
if (pendingFormat != null) {
formatQueue.add(presentationTimeUs, pendingFormat);
pendingFormat = null;
}
buffer.flip(); buffer.flip();
onQueueInputBuffer(buffer); onQueueInputBuffer(buffer);
@ -1012,6 +1031,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format; Format oldFormat = format;
format = newFormat; format = newFormat;
pendingFormat = newFormat;
boolean drmInitDataChanged = boolean drmInitDataChanged =
!Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData); !Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData);
@ -1234,7 +1254,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
} }
if (outputIndex >= 0) { 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;
}
/* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */
if (codecNeedsEosPropagation
&& (inputStreamEnded
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) {
processEndOfStream();
}
return false;
}
// We've dequeued a buffer. // We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) { if (shouldSkipAdaptationWorkaroundOutputBuffer) {
shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false;
@ -1245,7 +1281,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// The dequeued buffer indicates the end of the stream. Process it immediately. // The dequeued buffer indicates the end of the stream. Process it immediately.
processEndOfStream(); processEndOfStream();
return false; return false;
} else { }
this.outputIndex = outputIndex; this.outputIndex = outputIndex;
outputBuffer = getOutputBuffer(outputIndex); outputBuffer = getOutputBuffer(outputIndex);
// The dequeued buffer is a media buffer. Do some initial setup. // The dequeued buffer is a media buffer. Do some initial setup.
@ -1255,20 +1292,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
} }
shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs);
} Format format = formatQueue.pollFloor(outputBufferInfo.presentationTimeUs);
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) { if (format != null) {
processOutputFormat(); outputFormat = format;
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 */ {
if (codecNeedsEosPropagationWorkaround
&& (inputStreamEnded
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) {
processEndOfStream();
}
return false;
} }
} }
@ -1284,7 +1310,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputIndex, outputIndex,
outputBufferInfo.flags, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer); shouldSkipOutputBuffer,
outputFormat);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
processEndOfStream(); processEndOfStream();
if (outputStreamEnded) { if (outputStreamEnded) {
@ -1303,7 +1330,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputIndex, outputIndex,
outputBufferInfo.flags, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer); shouldSkipOutputBuffer,
outputFormat);
} }
if (processedOutputBuffer) { if (processedOutputBuffer) {
@ -1348,36 +1376,43 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/** /**
* Processes an output media buffer. * Processes an output media buffer.
* <p> *
* When a new {@link ByteBuffer} is passed to this method its position and limit delineate the * <p>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 * 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. * 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 * 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 * 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 * 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. * buffer's position to keep track of how much of the data it has processed.
* <p>
* 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 * <p>Note that the first call to this method following a call to {@link #onPositionReset(long,
* current iteration of the rendering loop. * boolean)} will always receive a new {@link ByteBuffer} to be processed.
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, *
* measured at the start of the current iteration of the rendering loop. * @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 codec The {@link MediaCodec} instance.
* @param buffer The output buffer to process. * @param buffer The output buffer to process.
* @param bufferIndex The index of the output buffer. * @param bufferIndex The index of the output buffer.
* @param bufferFlags The flags attached to the output buffer. * @param bufferFlags The flags attached to the output buffer.
* @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds. * @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds.
* @param shouldSkip Whether the buffer should be skipped (i.e. not rendered). * @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). * @return Whether the output buffer was fully processed (e.g. rendered or skipped).
* @throws ExoPlaybackException If an error occurs processing the output buffer. * @throws ExoPlaybackException If an error occurs processing the output buffer.
*/ */
protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, protected abstract boolean processOutputBuffer(
MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, long positionUs,
long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; long elapsedRealtimeUs,
MediaCodec codec,
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
long bufferPresentationTimeUs,
boolean shouldSkip,
Format format)
throws ExoPlaybackException;
/** /**
* Incrementally renders any remaining output. * Incrementally renders any remaining output.

View file

@ -499,23 +499,34 @@ public final class MediaCodecUtil {
*/ */
private static int avcLevelToMaxFrameSize(int avcLevel) { private static int avcLevelToMaxFrameSize(int avcLevel) {
switch (avcLevel) { switch (avcLevel) {
case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16; case CodecProfileLevel.AVCLevel1:
case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16; case CodecProfileLevel.AVCLevel1b:
case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16; return 99 * 16 * 16;
case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16; case CodecProfileLevel.AVCLevel12:
case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16; case CodecProfileLevel.AVCLevel13:
case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16; case CodecProfileLevel.AVCLevel2:
case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16; return 396 * 16 * 16;
case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16; case CodecProfileLevel.AVCLevel21:
case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16; return 792 * 16 * 16;
case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16; case CodecProfileLevel.AVCLevel22:
case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16; case CodecProfileLevel.AVCLevel3:
case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16; return 1620 * 16 * 16;
case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; case CodecProfileLevel.AVCLevel31:
case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; return 3600 * 16 * 16;
case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; case CodecProfileLevel.AVCLevel32:
case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16; return 5120 * 16 * 16;
default: return -1; 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;
} }
} }

View file

@ -32,6 +32,7 @@ import java.util.Arrays;
*/ */
public final class EventMessageDecoder implements MetadataDecoder { public final class EventMessageDecoder implements MetadataDecoder {
@SuppressWarnings("ByteBufferBackingArray")
@Override @Override
public Metadata decode(MetadataInputBuffer inputBuffer) { public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = inputBuffer.data; ByteBuffer buffer = inputBuffer.data;

View file

@ -56,13 +56,7 @@ public final class Id3Decoder implements MetadataDecoder {
/** A predicate that indicates no frames should be decoded. */ /** A predicate that indicates no frames should be decoded. */
public static final FramePredicate NO_FRAMES_PREDICATE = public static final FramePredicate NO_FRAMES_PREDICATE =
new FramePredicate() { (majorVersion, id0, id1, id2, id3) -> false;
@Override
public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) {
return false;
}
};
private static final String TAG = "Id3Decoder"; private static final String TAG = "Id3Decoder";
@ -102,6 +96,7 @@ public final class Id3Decoder implements MetadataDecoder {
this.framePredicate = framePredicate; this.framePredicate = framePredicate;
} }
@SuppressWarnings("ByteBufferBackingArray")
@Override @Override
public @Nullable Metadata decode(MetadataInputBuffer inputBuffer) { public @Nullable Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = inputBuffer.data; ByteBuffer buffer = inputBuffer.data;
@ -702,14 +697,13 @@ public final class Id3Decoder implements MetadataDecoder {
*/ */
private static String getCharsetName(int encodingByte) { private static String getCharsetName(int encodingByte) {
switch (encodingByte) { switch (encodingByte) {
case ID3_TEXT_ENCODING_ISO_8859_1:
return "ISO-8859-1";
case ID3_TEXT_ENCODING_UTF_16: case ID3_TEXT_ENCODING_UTF_16:
return "UTF-16"; return "UTF-16";
case ID3_TEXT_ENCODING_UTF_16BE: case ID3_TEXT_ENCODING_UTF_16BE:
return "UTF-16BE"; return "UTF-16BE";
case ID3_TEXT_ENCODING_UTF_8: case ID3_TEXT_ENCODING_UTF_8:
return "UTF-8"; return "UTF-8";
case ID3_TEXT_ENCODING_ISO_8859_1:
default: default:
return "ISO-8859-1"; return "ISO-8859-1";
} }

View file

@ -44,6 +44,7 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
sectionHeader = new ParsableBitArray(); sectionHeader = new ParsableBitArray();
} }
@SuppressWarnings("ByteBufferBackingArray")
@Override @Override
public Metadata decode(MetadataInputBuffer inputBuffer) { public Metadata decode(MetadataInputBuffer inputBuffer) {
// Internal timestamps adjustment. // Internal timestamps adjustment.

View file

@ -189,6 +189,7 @@ public abstract class DownloadAction {
public abstract Downloader createDownloader( public abstract Downloader createDownloader(
DownloaderConstructorHelper downloaderConstructorHelper); DownloaderConstructorHelper downloaderConstructorHelper);
@SuppressWarnings("EqualsGetClass")
@Override @Override
public boolean equals(@Nullable Object o) { public boolean equals(@Nullable Object o) {
if (o == null || getClass() != o.getClass()) { if (o == null || getClass() != o.getClass()) {

View file

@ -59,21 +59,9 @@ public abstract class DownloadHelper {
public void run() { public void run() {
try { try {
prepareInternal(); prepareInternal();
handler.post( handler.post(() -> callback.onPrepared(DownloadHelper.this));
new Runnable() {
@Override
public void run() {
callback.onPrepared(DownloadHelper.this);
}
});
} catch (final IOException e) { } catch (final IOException e) {
handler.post( handler.post(() -> callback.onPrepareError(DownloadHelper.this, e));
new Runnable() {
@Override
public void run() {
callback.onPrepareError(DownloadHelper.this, e);
}
});
} }
} }
}.start(); }.start();

View file

@ -336,12 +336,7 @@ public final class DownloadManager {
tasks.get(i).stop(); tasks.get(i).stop();
} }
final ConditionVariable fileIOFinishedCondition = new ConditionVariable(); final ConditionVariable fileIOFinishedCondition = new ConditionVariable();
fileIOHandler.post(new Runnable() { fileIOHandler.post(fileIOFinishedCondition::open);
@Override
public void run() {
fileIOFinishedCondition.open();
}
});
fileIOFinishedCondition.block(); fileIOFinishedCondition.block();
fileIOThread.quit(); fileIOThread.quit();
logd("Released"); logd("Released");
@ -451,9 +446,7 @@ public final class DownloadManager {
private void loadActions() { private void loadActions() {
fileIOHandler.post( fileIOHandler.post(
new Runnable() { () -> {
@Override
public void run() {
DownloadAction[] loadedActions; DownloadAction[] loadedActions;
try { try {
loadedActions = actionFile.load(DownloadManager.this.deserializers); loadedActions = actionFile.load(DownloadManager.this.deserializers);
@ -464,9 +457,7 @@ public final class DownloadManager {
} }
final DownloadAction[] actions = loadedActions; final DownloadAction[] actions = loadedActions;
handler.post( handler.post(
new Runnable() { () -> {
@Override
public void run() {
if (released) { if (released) {
return; return;
} }
@ -493,9 +484,7 @@ public final class DownloadManager {
notifyListenersTaskStateChange(task); notifyListenersTaskStateChange(task);
} }
} }
}
}); });
}
}); });
} }
@ -507,16 +496,14 @@ public final class DownloadManager {
for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) {
actions[i] = tasks.get(i).action; actions[i] = tasks.get(i).action;
} }
fileIOHandler.post(new Runnable() { fileIOHandler.post(
@Override () -> {
public void run() {
try { try {
actionFile.store(actions); actionFile.store(actions);
logd("Actions persisted."); logd("Actions persisted.");
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Persisting actions failed.", e); Log.e(TAG, "Persisting actions failed.", e);
} }
}
}); });
} }
@ -534,7 +521,8 @@ public final class DownloadManager {
public static final class TaskState { 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}.
* *
* <p>Transition diagram: * <p>Transition diagram:
* *
@ -614,7 +602,10 @@ public final class DownloadManager {
private static final class Task implements Runnable { 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}.
* *
* <p>Transition map (vertical states are source states): * <p>Transition map (vertical states are source states):
* *
@ -771,12 +762,7 @@ public final class DownloadManager {
private void cancel() { private void cancel() {
if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) { if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) {
downloadManager.handler.post( downloadManager.handler.post(
new Runnable() { () -> changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED));
@Override
public void run() {
changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED);
}
});
} else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) { } else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) {
cancelDownload(); cancelDownload();
} }
@ -851,19 +837,14 @@ public final class DownloadManager {
} }
final Throwable finalError = error; final Throwable finalError = error;
downloadManager.handler.post( downloadManager.handler.post(
new Runnable() { () -> {
@Override
public void run() {
if (changeStateAndNotify( if (changeStateAndNotify(
STATE_STARTED, STATE_STARTED, finalError != null ? STATE_FAILED : STATE_COMPLETED, finalError)
finalError != null ? STATE_FAILED : STATE_COMPLETED,
finalError)
|| changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED) || changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED)
|| changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) { || changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) {
return; return;
} }
throw new IllegalStateException(); throw new IllegalStateException();
}
}); });
} }

Some files were not shown because too many files have changed in this diff Show more