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
transitions with small increases in maximum sample size don't require
reinitialization ([#4510](https://github.com/google/ExoPlayer/issues/4510)).
* Propagate the end-of-stream signal directly in the renderer when using
tunneling, to fix an issue where the player would remain ready after the
stream ended.
* Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when
creating a `CacheDataSource`.
* Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend
@ -70,6 +73,8 @@
* Allow configuration of the Loader retry delay
([#3370](https://github.com/google/ExoPlayer/issues/3370)).
* HLS:
* Add support for variable substitution
([#4422](https://github.com/google/ExoPlayer/issues/4422)).
* Add support for PlayReady.
* Add support for alternative EXT-X-KEY tags.
* Set the bitrate on primary track sample formats
@ -91,7 +96,10 @@
* Allow setting the `Looper`, which is used to access the player, in
`ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)).
* Use default Deserializers if non given to DownloadManager.
* Add monoscopic 360 surface type to PlayerView.
* 360:
* Add monoscopic 360 surface type to PlayerView.
* Support
[VR180 video format](https://github.com/google/spatial-media/blob/master/docs/vr180.md).
* Deprecate `Player.DefaultEventListener` as selective listener overrides can
be directly made with the `Player.EventListener` interface.
* Deprecate `DefaultAnalyticsListener` as selective listener overrides can be
@ -99,6 +107,10 @@
* Add uri field to `LoadEventInfo` in `MediaSourceEventListener` or
`AnalyticsListener` callbacks. This uri is the redirected uri if redirection
occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)).
* Add response headers field to `LoadEventInfo` in `MediaSourceEventListener` or
`AnalyticsListener` callbacks
([#4361](https://github.com/google/ExoPlayer/issues/4361) and
[#4615](https://github.com/google/ExoPlayer/issues/4615)).
* Allow `MediaCodecSelector`s to return multiple compatible decoders for
`MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that
falls back to less preferred decoders like `MediaCodec.createDecoderByType`
@ -110,6 +122,14 @@
* Add option to show buffering view when playWhenReady is false
([#4304](https://github.com/google/ExoPlayer/issues/4304)).
* Allow any `Drawable` to be used as `PlayerView` default artwork.
* IMA:
* Refine the previous fix for empty ad groups to avoid discarding ad breaks
unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030)),
([#4280](https://github.com/google/ExoPlayer/issues/4280)).
* Fix handling of empty postrolls
([#4681](https://github.com/google/ExoPlayer/issues/4681).
* Fix handling of postrolls with multiple ads
([#4710](https://github.com/google/ExoPlayer/issues/4710).
### 2.8.4 ###

View file

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

View file

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

View file

@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<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.hardware.touchscreen" android:required="false"/>

View file

@ -567,6 +567,11 @@
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4",
"spherical_stereo_mode": "top_bottom"
},
{
"name": "Sphericalv2 (180 top-bottom stereo)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/sphericalv2.mp4",
"spherical_stereo_mode": "top_bottom"
},
{
"name": "Iceland (360 top-bottom stereo ts)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts",

View file

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

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

View file

@ -22,6 +22,7 @@ import android.content.res.AssetManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.JsonReader;
import android.util.Log;
import android.view.Menu;
@ -162,8 +163,8 @@ public class SampleChooserActivity extends Activity
startActivity(
sample.buildIntent(
/* context= */ this,
preferExtensionDecodersMenuItem.isChecked(),
randomAbrMenuItem.isChecked()
isNonNullAndChecked(preferExtensionDecodersMenuItem),
isNonNullAndChecked(randomAbrMenuItem)
? PlayerActivity.ABR_ALGORITHM_RANDOM
: PlayerActivity.ABR_ALGORITHM_DEFAULT));
return true;
@ -198,6 +199,11 @@ public class SampleChooserActivity extends Activity
return 0;
}
private static boolean isNonNullAndChecked(@Nullable MenuItem menuItem) {
// Temporary workaround for layouts that do not inflate the options menu.
return menuItem != null && menuItem.isChecked();
}
private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> {
private boolean sawError;
@ -207,7 +213,8 @@ public class SampleChooserActivity extends Activity
List<SampleGroup> result = new ArrayList<>();
Context context = getApplicationContext();
String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
DataSource dataSource = new DefaultDataSource(context, null, userAgent, false);
DataSource dataSource =
new DefaultDataSource(context, userAgent, /* allowCrossProtocolRedirects= */ false);
for (String uri : uris) {
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);

View file

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

View file

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

View file

@ -606,11 +606,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
if (request != currentUrlRequest) {
return;
}
if (currentDataSpec.postBody != null) {
if (currentDataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) {
int responseCode = info.getHttpStatusCode();
// The industry standard is to disregard POST redirects when the status code is 307 or 308.
// For other redirect response codes the POST request is converted to a GET request and the
// redirect is followed.
if (responseCode == 307 || responseCode == 308) {
exception =
new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec);
@ -627,7 +625,23 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
request.followRedirect();
} else {
currentUrlRequest.cancel();
DataSpec redirectUrlDataSpec = currentDataSpec.withUri(Uri.parse(newLocationUrl));
DataSpec redirectUrlDataSpec;
if (currentDataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) {
// For POST redirects that aren't 307 or 308, the redirect is followed but request is
// transformed into a GET.
redirectUrlDataSpec =
new DataSpec(
Uri.parse(newLocationUrl),
DataSpec.HTTP_METHOD_GET,
/* httpBody= */ null,
currentDataSpec.absoluteStreamPosition,
currentDataSpec.position,
currentDataSpec.length,
currentDataSpec.key,
currentDataSpec.flags);
} else {
redirectUrlDataSpec = currentDataSpec.withUri(Uri.parse(newLocationUrl));
}
UrlRequest.Builder requestBuilder;
try {
requestBuilder = buildRequestBuilder(redirectUrlDataSpec);

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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. */
@SuppressWarnings("ByteBufferBackingArray")
public void decodeSample(ByteBuffer output)
throws IOException, InterruptedException, FlacFrameDecodeException {
output.clear();

View file

@ -50,7 +50,10 @@ public final class FlacExtractor implements Extractor {
/** Factory that returns one extractor which is a {@link FlacExtractor}. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlacExtractor()};
/** Flags controlling the behavior of the extractor. */
/**
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_DISABLE_ID3_METADATA}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,

View file

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

View file

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

View file

@ -631,11 +631,8 @@ public final class ImaAdsLoader
} else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs;
int adGroupIndexForPosition =
expectedAdGroupIndex =
adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
if (adGroupIndexForPosition != C.INDEX_UNSET) {
expectedAdGroupIndex = adGroupIndexForPosition;
}
} else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) {
contentPositionMs = player.getCurrentPosition();
// Update the expected ad group index for the current content position. The update is delayed
@ -867,8 +864,6 @@ public final class ImaAdsLoader
&& playWhenReady) {
checkForContentComplete();
} else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
@ -1044,26 +1039,24 @@ public final class ImaAdsLoader
int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup;
playingAd = player.isPlayingAd();
playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET;
if (!sentContentComplete) {
boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup;
if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
if (DEBUG) {
Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity");
}
boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup;
if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
if (!wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) {
int adGroupIndex = player.getCurrentAdGroupIndex();
// IMA hasn't called playAd yet, so fake the content position.
fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime();
fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) {
fakeContentProgressOffsetMs = contentDurationMs;
}
if (DEBUG) {
Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity");
}
}
if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) {
int adGroupIndex = player.getCurrentAdGroupIndex();
// IMA hasn't called playAd yet, so fake the content position.
fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime();
fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) {
fakeContentProgressOffsetMs = contentDurationMs;
}
}
}
@ -1127,16 +1120,8 @@ public final class ImaAdsLoader
if (pendingAdLoadError == null) {
pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex);
}
// Discard the ad break, which makes sure we don't receive duplicate load error events.
adsManager.discardAdBreak();
// Set the next expected ad group index so we can handle multiple load errors in a row.
adGroupIndex++;
if (adGroupIndex < adPlaybackState.adGroupCount) {
expectedAdGroupIndex = adGroupIndex;
} else {
expectedAdGroupIndex = C.INDEX_UNSET;
}
pendingContentPositionMs = C.TIME_UNSET;
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
}
private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) {
@ -1184,6 +1169,10 @@ public final class ImaAdsLoader
Log.d(TAG, "adsLoader.contentComplete");
}
sentContentComplete = true;
// After sending content complete IMA will not poll the content position, so set the expected
// ad group index.
expectedAdGroupIndex =
adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentDurationMs));
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -39,7 +39,7 @@ class CombinedJavadocPlugin implements Plugin<Project> {
libraryModules.each { libraryModule ->
libraryModule.android.libraryVariants.all { variant ->
def name = variant.buildType.name
if (name.equals("release")) {
if (name == "release") {
classpath +=
libraryModule.project.files(
variant.javaCompile.classpath.files,
@ -63,7 +63,7 @@ class CombinedJavadocPlugin implements Plugin<Project> {
}
// 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 {
it.plugins.findPlugin("com.android.library") &&
it.tasks.findByName("generateJavadoc")

View file

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

View file

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

View file

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

View file

@ -26,6 +26,8 @@ import android.view.Surface;
import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
@ -109,7 +111,8 @@ public final class C {
public static final String SANS_SERIF_NAME = "sans-serif";
/**
* Crypto modes for a codec.
* Crypto modes for a codec. One of {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR}
* or {@link #CRYPTO_MODE_AES_CBC}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
@ -133,7 +136,14 @@ public final class C {
*/
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
/** Represents an audio encoding, or an invalid or unset value. */
/**
* Represents an audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
* #ENCODING_DOLBY_TRUEHD}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
@ -153,7 +163,12 @@ public final class C {
})
public @interface Encoding {}
/** Represents a PCM audio encoding, or an invalid or unset value. */
/**
* Represents a PCM audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
@ -195,11 +210,22 @@ public final class C {
public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
/**
* Stream types for an {@link android.media.AudioTrack}.
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
* #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link
* #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link
* #STREAM_TYPE_USE_DEFAULT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_DTMF, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION,
STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, STREAM_TYPE_USE_DEFAULT})
@IntDef({
STREAM_TYPE_ALARM,
STREAM_TYPE_DTMF,
STREAM_TYPE_MUSIC,
STREAM_TYPE_NOTIFICATION,
STREAM_TYPE_RING,
STREAM_TYPE_SYSTEM,
STREAM_TYPE_VOICE_CALL,
STREAM_TYPE_USE_DEFAULT
})
public @interface StreamType {}
/**
* @see AudioManager#STREAM_ALARM
@ -239,11 +265,18 @@ public final class C {
public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
/**
* Content types for {@link com.google.android.exoplayer2.audio.AudioAttributes}.
* Content types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. One of {@link
* #CONTENT_TYPE_MOVIE}, {@link #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link
* #CONTENT_TYPE_SPEECH} or {@link #CONTENT_TYPE_UNKNOWN}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, CONTENT_TYPE_SONIFICATION, CONTENT_TYPE_SPEECH,
CONTENT_TYPE_UNKNOWN})
@IntDef({
CONTENT_TYPE_MOVIE,
CONTENT_TYPE_MUSIC,
CONTENT_TYPE_SONIFICATION,
CONTENT_TYPE_SPEECH,
CONTENT_TYPE_UNKNOWN
})
public @interface AudioContentType {}
/**
* @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE
@ -270,13 +303,16 @@ public final class C {
android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
/**
* Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}.
* <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.
* Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}. Possible flag value is
* {@link #FLAG_AUDIBILITY_ENFORCED}.
*
* <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)
@IntDef(flag = true, value = {FLAG_AUDIBILITY_ENFORCED})
@IntDef(
flag = true,
value = {FLAG_AUDIBILITY_ENFORCED})
public @interface AudioFlags {}
/**
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
@ -284,7 +320,17 @@ public final class C {
public static final int FLAG_AUDIBILITY_ENFORCED =
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
/** Usage types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. */
/**
* Usage types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. One of {@link
* #USAGE_ALARM}, {@link #USAGE_ASSISTANCE_ACCESSIBILITY}, {@link
* #USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, {@link #USAGE_ASSISTANCE_SONIFICATION}, {@link
* #USAGE_ASSISTANT}, {@link #USAGE_GAME}, {@link #USAGE_MEDIA}, {@link #USAGE_NOTIFICATION},
* {@link #USAGE_NOTIFICATION_COMMUNICATION_DELAYED}, {@link
* #USAGE_NOTIFICATION_COMMUNICATION_INSTANT}, {@link #USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
* {@link #USAGE_NOTIFICATION_EVENT}, {@link #USAGE_NOTIFICATION_RINGTONE}, {@link
* #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link
* #USAGE_VOICE_COMMUNICATION_SIGNALLING}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
USAGE_ALARM,
@ -376,7 +422,11 @@ public final class C {
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
/** Audio focus types. */
/**
* Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
* #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AUDIOFOCUS_NONE,
@ -400,11 +450,19 @@ public final class C {
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
/**
* Flags which can apply to a buffer containing a media sample.
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and
* {@link #BUFFER_FLAG_DECODE_ONLY}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY})
@IntDef(
flag = true,
value = {
BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_ENCRYPTED,
BUFFER_FLAG_DECODE_ONLY
})
public @interface BufferFlags {}
/**
* Indicates that a buffer holds a synchronization sample.
@ -417,10 +475,12 @@ public final class C {
/** Indicates that a buffer is (at least partially) encrypted. */
public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000
/** Indicates that a buffer should be decoded but not rendered. */
@SuppressWarnings("NumericOverflow")
public static final int BUFFER_FLAG_DECODE_ONLY = 1 << 31; // 0x80000000
/**
* Video scaling modes for {@link MediaCodec}-based {@link Renderer}s.
* Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. One of {@link
* #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING})
@ -441,11 +501,13 @@ public final class C {
public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT;
/**
* Track selection flags.
* Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link
* #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED,
SELECTION_FLAG_AUTOSELECT})
@IntDef(
flag = true,
value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, SELECTION_FLAG_AUTOSELECT})
public @interface SelectionFlags {}
/**
* Indicates that the track should be selected if user preferences do not state otherwise.
@ -465,7 +527,8 @@ public final class C {
public static final String LANGUAGE_UNDETERMINED = "und";
/**
* Represents a streaming or other media type.
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link
* #TYPE_HLS} or {@link #TYPE_OTHER}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
@ -533,34 +596,22 @@ public final class C {
*/
public static final int DATA_TYPE_CUSTOM_BASE = 10000;
/**
* A type constant for tracks of unknown type.
*/
/** A type constant for tracks of unknown type. */
public static final int TRACK_TYPE_UNKNOWN = -1;
/**
* A type constant for tracks of some default type, where the type itself is unknown.
*/
/** A type constant for tracks of some default type, where the type itself is unknown. */
public static final int TRACK_TYPE_DEFAULT = 0;
/**
* A type constant for audio tracks.
*/
/** A type constant for audio tracks. */
public static final int TRACK_TYPE_AUDIO = 1;
/**
* A type constant for video tracks.
*/
/** A type constant for video tracks. */
public static final int TRACK_TYPE_VIDEO = 2;
/**
* A type constant for text tracks.
*/
/** A type constant for text tracks. */
public static final int TRACK_TYPE_TEXT = 3;
/**
* A type constant for metadata tracks.
*/
/** A type constant for metadata tracks. */
public static final int TRACK_TYPE_METADATA = 4;
/**
* A type constant for a dummy or empty track.
*/
public static final int TRACK_TYPE_NONE = 5;
/** A type constant for camera motion tracks. */
public static final int TRACK_TYPE_CAMERA_MOTION = 5;
/** A type constant for a dummy or empty track. */
public static final int TRACK_TYPE_NONE = 6;
/**
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
* equal to this value.
@ -593,55 +644,42 @@ public final class C {
*/
public static final int SELECTION_REASON_CUSTOM_BASE = 10000;
/**
* A default size in bytes for an individual allocation that forms part of a larger buffer.
*/
/** A default size in bytes for an individual allocation that forms part of a larger buffer. */
public static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024;
/**
* A default size in bytes for a video buffer.
*/
/** A default size in bytes for a video buffer. */
public static final int DEFAULT_VIDEO_BUFFER_SIZE = 200 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for an audio buffer.
*/
/** A default size in bytes for an audio buffer. */
public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for a text buffer.
*/
/** A default size in bytes for a text buffer. */
public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for a metadata buffer.
*/
/** A default size in bytes for a metadata buffer. */
public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* A default size in bytes for a muxed buffer (e.g. containing video, audio and text).
*/
public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE
+ DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE;
/** A default size in bytes for a camera motion buffer. */
public static final int DEFAULT_CAMERA_MOTION_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE;
/**
* "cenc" scheme type name as defined in ISO/IEC 23001-7:2016.
*/
/** A default size in bytes for a muxed buffer (e.g. containing video, audio and text). */
public static final int DEFAULT_MUXED_BUFFER_SIZE =
DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE;
/** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */
@SuppressWarnings("ConstantField")
public static final String CENC_TYPE_cenc = "cenc";
/**
* "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016.
*/
/** "cbc1" scheme type name as defined in ISO/IEC 23001-7:2016. */
@SuppressWarnings("ConstantField")
public static final String CENC_TYPE_cbc1 = "cbc1";
/**
* "cens" scheme type name as defined in ISO/IEC 23001-7:2016.
*/
/** "cens" scheme type name as defined in ISO/IEC 23001-7:2016. */
@SuppressWarnings("ConstantField")
public static final String CENC_TYPE_cens = "cens";
/**
* "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016.
*/
/** "cbcs" scheme type name as defined in ISO/IEC 23001-7:2016. */
@SuppressWarnings("ConstantField")
public static final String CENC_TYPE_cbcs = "cbcs";
/**
@ -733,6 +771,20 @@ public final class C {
*/
public static final int MSG_SET_AUX_EFFECT_INFO = 5;
/**
* The type of a message that can be passed to a video {@link Renderer} via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be a {@link
* VideoFrameMetadataListener} instance, or null.
*/
public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = 6;
/**
* The type of a message that can be passed to a camera motion {@link Renderer} via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be a {@link CameraMotionListener}
* instance, or null.
*/
public static final int MSG_SET_CAMERA_MOTION_LISTENER = 7;
/**
* Applications or extensions may define custom {@code MSG_*} constants that can be passed to
* {@link Renderer}s. These custom constants must be greater than or equal to this value.
@ -740,15 +792,17 @@ public final class C {
public static final int MSG_CUSTOM_BASE = 10000;
/**
* The stereo mode for 360/3D/VR videos.
* The stereo mode for 360/3D/VR videos. One of {@link Format#NO_VALUE}, {@link
* #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link
* #STEREO_MODE_STEREO_MESH}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
Format.NO_VALUE,
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
})
public @interface StereoMode {}
/**
@ -770,7 +824,8 @@ public final class C {
public static final int STEREO_MODE_STEREO_MESH = 3;
/**
* Video colorspaces.
* Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link
* #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
@ -789,7 +844,8 @@ public final class C {
public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
/**
* Video color transfer characteristics.
* Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
* #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
@ -808,7 +864,8 @@ public final class C {
public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
/**
* Video color range.
* Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link
* #COLOR_RANGE_FULL}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
@ -836,7 +893,12 @@ public final class C {
*/
public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000;
/** Network connection type. */
/**
* Network connection type. One of {@link #NETWORK_TYPE_UNKNOWN}, {@link #NETWORK_TYPE_OFFLINE},
* {@link #NETWORK_TYPE_WIFI}, {@link #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, {@link
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or
* {@link #NETWORK_TYPE_OTHER}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
NETWORK_TYPE_UNKNOWN,

View file

@ -154,6 +154,7 @@ public class DefaultLoadControl implements LoadControl {
}
/** Creates a {@link DefaultLoadControl}. */
@SuppressWarnings("deprecation")
public DefaultLoadControl createDefaultLoadControl() {
if (allocator == null) {
allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
@ -183,15 +184,15 @@ public class DefaultLoadControl implements LoadControl {
private int targetBufferSize;
private boolean isBuffering;
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*/
/** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */
@SuppressWarnings("deprecation")
public DefaultLoadControl() {
this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE));
}
/** @deprecated Use {@link Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public DefaultLoadControl(DefaultAllocator allocator) {
this(
allocator,
@ -205,6 +206,7 @@ public class DefaultLoadControl implements LoadControl {
/** @deprecated Use {@link Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public DefaultLoadControl(
DefaultAllocator allocator,
int minBufferMs,

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

View file

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

View file

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

View file

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

View file

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

View file

@ -62,20 +62,6 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
this.isFinal = isFinal;
}
/**
* Returns a copy of this instance with the period identifier's period index set to the specified
* value.
*/
public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) {
return new MediaPeriodInfo(
id.copyWithPeriodIndex(periodIndex),
startPositionUs,
contentPositionUs,
durationUs,
isLastInTimelinePeriod,
isFinal);
}
/** Returns a copy of this instance with the start position set to the specified value. */
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo(

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

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
* is used when playback infos are created with {@link #createDummy(long, TrackSelectorResult)}.
*/
public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = new MediaPeriodId(/* periodIndex= */ 0);
public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID =
new MediaPeriodId(/* periodUid= */ new Object());
/** The current {@link Timeline}. */
public final Timeline timeline;
@ -149,23 +150,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs);
}
public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
return new PlaybackInfo(
timeline,
manifest,
periodId.copyWithPeriodIndex(periodIndex),
startPositionUs,
contentPositionUs,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
return new PlaybackInfo(
timeline,

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

View file

@ -156,6 +156,14 @@ public final class PlayerMessage {
return handler;
}
/**
* Returns position in window at {@link #getWindowIndex()} at which the message will be delivered,
* in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately.
*/
public long getPositionMs() {
return positionMs;
}
/**
* Sets a position in the current window at which the message will be delivered.
*
@ -170,14 +178,6 @@ public final class PlayerMessage {
return this;
}
/**
* Returns position in window at {@link #getWindowIndex()} at which the message will be delivered,
* in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately.
*/
public long getPositionMs() {
return positionMs;
}
/**
* Sets a position in a window at which the message will be delivered.
*
@ -231,7 +231,7 @@ public final class PlayerMessage {
* Player.EventListener#onPlayerError(ExoPlaybackException)}.
*
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
* @throws IllegalStateException If this message has already been sent.
*/
public PlayerMessage send() {
Assertions.checkState(!isSent);

View file

@ -34,7 +34,10 @@ import java.lang.annotation.RetentionPolicy;
*/
public interface Renderer extends PlayerMessage.Target {
/** The renderer states. */
/**
* The renderer states. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} or {@link
* #STATE_STARTED}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
@interface State {}
@ -202,7 +205,7 @@ public interface Renderer extends PlayerMessage.Target {
* @param operatingRate The operating rate.
* @throws ExoPlaybackException If an error occurs handling the operating rate.
*/
default void setOperatingRate(float operatingRate) throws ExoPlaybackException {};
default void setOperatingRate(float operatingRate) throws ExoPlaybackException {}
/**
* Incrementally renders the {@link SampleStream}.

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.util.Clock;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -105,39 +107,8 @@ public class SimpleExoPlayer
private float audioVolume;
private MediaSource mediaSource;
private List<Cue> currentCues;
/**
* @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);
}
private VideoFrameMetadataListener videoFrameMetadataListener;
private CameraMotionListener cameraMotionListener;
/**
* @param context A {@link Context}.
@ -317,6 +288,13 @@ public class SimpleExoPlayer
setVideoSurface(null);
}
@Override
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
@Override
public void setVideoSurface(Surface surface) {
removeSurfaceCallbacks();
@ -325,13 +303,6 @@ public class SimpleExoPlayer
maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize);
}
@Override
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
@Override
public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
removeSurfaceCallbacks();
@ -598,6 +569,66 @@ public class SimpleExoPlayer
videoListeners.remove(listener);
}
@Override
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
videoFrameMetadataListener = listener;
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) {
player
.createMessage(renderer)
.setType(C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(listener)
.send();
}
}
}
@Override
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
if (videoFrameMetadataListener != listener) {
return;
}
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) {
player
.createMessage(renderer)
.setType(C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(null)
.send();
}
}
}
@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.
*
@ -605,6 +636,7 @@ public class SimpleExoPlayer
* @deprecated Use {@link #addVideoListener(com.google.android.exoplayer2.video.VideoListener)}.
*/
@Deprecated
@SuppressWarnings("deprecation")
public void setVideoListener(VideoListener listener) {
videoListeners.clear();
if (listener != null) {
@ -620,6 +652,7 @@ public class SimpleExoPlayer
* #removeVideoListener(com.google.android.exoplayer2.video.VideoListener)}.
*/
@Deprecated
@SuppressWarnings("deprecation")
public void clearVideoListener(VideoListener listener) {
removeVideoListener(listener);
}
@ -710,6 +743,7 @@ public class SimpleExoPlayer
* information.
*/
@Deprecated
@SuppressWarnings("deprecation")
public void setVideoDebugListener(VideoRendererEventListener listener) {
videoDebugListeners.retainAll(Collections.singleton(analyticsCollector));
if (listener != null) {
@ -740,6 +774,7 @@ public class SimpleExoPlayer
* information.
*/
@Deprecated
@SuppressWarnings("deprecation")
public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListeners.retainAll(Collections.singleton(analyticsCollector));
if (listener != null) {
@ -940,6 +975,8 @@ public class SimpleExoPlayer
}
@Override
@Deprecated
@SuppressWarnings("deprecation")
public void sendMessages(ExoPlayerMessage... messages) {
player.sendMessages(messages);
}
@ -950,6 +987,8 @@ public class SimpleExoPlayer
}
@Override
@Deprecated
@SuppressWarnings("deprecation")
public void blockingSendMessages(ExoPlayerMessage... messages) {
player.blockingSendMessages(messages);
}
@ -1054,6 +1093,11 @@ public class SimpleExoPlayer
return player.getCurrentAdIndexInAdGroup();
}
@Override
public long getContentDuration() {
return player.getContentDuration();
}
@Override
public long 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
* projection.
*/
public final Pair<Integer, Long> getPeriodPosition(Window window, Period period, int windowIndex,
long windowPositionUs) {
public final Pair<Object, Long> getPeriodPosition(
Window window, Period period, int windowIndex, long windowPositionUs) {
return getPeriodPosition(window, period, windowIndex, windowPositionUs, 0);
}
/**
* Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs).
* Converts (windowIndex, windowPositionUs) to the corresponding (periodUid, periodPositionUs).
*
* @param window A {@link Window} that may be overwritten.
* @param period A {@link Period} that may be overwritten.
@ -717,12 +717,16 @@ public abstract class Timeline {
* start position.
* @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
* duration into the future by which the window's position should be projected.
* @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs}
* @return The corresponding (periodUid, periodPositionUs), or null if {@code #windowPositionUs}
* is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
* position could not be projected by {@code defaultPositionProjectionUs}.
*/
public final Pair<Integer, Long> getPeriodPosition(Window window, Period period, int windowIndex,
long windowPositionUs, long defaultPositionProjectionUs) {
public final Pair<Object, Long> getPeriodPosition(
Window window,
Period period,
int windowIndex,
long windowPositionUs,
long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, getWindowCount());
getWindow(windowIndex, window, false, defaultPositionProjectionUs);
if (windowPositionUs == C.TIME_UNSET) {
@ -733,13 +737,13 @@ public abstract class Timeline {
}
int periodIndex = window.firstPeriodIndex;
long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs;
long periodDurationUs = getPeriod(periodIndex, period).getDurationUs();
long periodDurationUs = getPeriod(periodIndex, period, /* setIds= */ true).getDurationUs();
while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
&& periodIndex < window.lastPeriodIndex) {
periodPositionUs -= periodDurationUs;
periodDurationUs = getPeriod(++periodIndex, period).getDurationUs();
periodDurationUs = getPeriod(++periodIndex, period, /* setIds= */ true).getDurationUs();
}
return Pair.create(periodIndex, periodPositionUs);
return Pair.create(period.uid, periodPositionUs);
}
/**

View file

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

View file

@ -33,7 +33,10 @@ public final class Ac3Util {
/** Holds sample format information as presented by a syncframe header. */
public static final class SyncFrameInfo {
/** AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. */
/**
* AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED},
* {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2})
public @interface StreamType {}

View file

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

View file

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

View file

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

View file

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

View file

@ -27,11 +27,16 @@ import java.nio.ByteBuffer;
public class DecoderInputBuffer extends Buffer {
/**
* The buffer replacement mode, which may disable replacement.
* The buffer replacement mode, which may disable replacement. One of {@link
* #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link
* #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL,
BUFFER_REPLACEMENT_MODE_DIRECT})
@IntDef({
BUFFER_REPLACEMENT_MODE_DISABLED,
BUFFER_REPLACEMENT_MODE_NORMAL,
BUFFER_REPLACEMENT_MODE_DIRECT
})
public @interface BufferReplacementMode {}
/**
* Disallows buffer replacement.
@ -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
* 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}
* 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
* the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}.
*/
public void ensureSpaceForWrite(int length) throws IllegalStateException {
public void ensureSpaceForWrite(int length) {
if (data == null) {
data = createReplacementByteBuffer(length);
return;

View file

@ -20,11 +20,11 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayDeque;
/**
* Base class for {@link Decoder}s that use their own decode thread.
*/
public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer,
E extends Exception> implements Decoder<I, O, E> {
/** Base class for {@link Decoder}s that use their own decode thread. */
@SuppressWarnings("UngroupedOverloads")
public abstract class SimpleDecoder<
I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception>
implements Decoder<I, O, E> {
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";
/** Determines the action to be done after a session acquired. */
/**
* Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK},
* {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE})
public @interface Mode {}

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)
@IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
public @interface State {}
@interface State {}
/**
* The session has been released.
*/

View file

@ -24,8 +24,6 @@ import android.media.MediaDrm;
import android.media.MediaDrmException;
import android.media.NotProvisionedException;
import android.media.UnsupportedSchemeException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer2.util.Assertions;
@ -68,10 +66,10 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
private FrameworkMediaDrm(UUID uuid) throws UnsupportedSchemeException {
Assertions.checkNotNull(uuid);
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.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()) {
forceWidevineL3(mediaDrm);
}
@ -80,13 +78,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
@Override
public void setOnEventListener(
final ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto> listener) {
mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() {
@Override
public void onEvent(@NonNull MediaDrm md, @Nullable byte[] sessionId, int event, int extra,
byte[] data) {
listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data);
}
});
mediaDrm.setOnEventListener(
listener == null
? null
: (mediaDrm, sessionId, event, extra, data) ->
listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data));
}
@Override
@ -99,20 +95,13 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
mediaDrm.setOnKeyStatusChangeListener(
listener == null
? null
: new MediaDrm.OnKeyStatusChangeListener() {
@Override
public void onKeyStatusChange(
@NonNull MediaDrm md,
@NonNull byte[] sessionId,
@NonNull List<MediaDrm.KeyStatus> keyInfo,
boolean hasNewUsableKey) {
List<KeyStatus> exoKeyInfo = new ArrayList<>();
for (MediaDrm.KeyStatus keyStatus : keyInfo) {
exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId()));
}
listener.onKeyStatusChange(
FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey);
: (mediaDrm, sessionId, keyInfo, hasNewUsableKey) -> {
List<KeyStatus> exoKeyInfo = new ArrayList<>();
for (MediaDrm.KeyStatus keyStatus : keyInfo) {
exoKeyInfo.add(new KeyStatus(keyStatus.getStatusCode(), keyStatus.getKeyId()));
}
listener.onKeyStatusChange(
FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey);
},
null);
}
@ -238,7 +227,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
}
@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");
}

View file

@ -25,7 +25,8 @@ import java.lang.annotation.RetentionPolicy;
public final class UnsupportedDrmException extends Exception {
/**
* The reason for the exception.
* The reason for the exception. One of {@link #REASON_UNSUPPORTED_SCHEME} or {@link
* #REASON_INSTANTIATION_ERROR}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})

View file

@ -95,9 +95,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
*
* @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate
* assumption should be enabled for all extractors that support it.
* @return The factory, for convenience.
*/
public void setConstantBitrateSeekingEnabled(boolean constantBitrateSeekingEnabled) {
public synchronized DefaultExtractorsFactory setConstantBitrateSeekingEnabled(
boolean constantBitrateSeekingEnabled) {
this.constantBitrateSeekingEnabled = constantBitrateSeekingEnabled;
return this;
}
/**

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;
/** Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. */
/**
* Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. One of
* {@link #RESULT_CONTINUE}, {@link #RESULT_SEEK} or {@link #RESULT_END_OF_INPUT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT})
@interface ReadResult {}

View file

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

View file

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

View file

@ -47,7 +47,10 @@ public final class AmrExtractor implements Extractor {
/** Factory for {@link AmrExtractor} instances. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AmrExtractor()};
/** Flags controlling the behavior of the extractor. */
/**
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,

View file

@ -27,7 +27,10 @@ import java.lang.annotation.RetentionPolicy;
*/
/* package */ interface EbmlReaderOutput {
/** EBML element types. */
/**
* EBML element types. One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link
* #TYPE_UNSIGNED_INT}, {@link #TYPE_STRING}, {@link #TYPE_BINARY} or {@link #TYPE_FLOAT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT})
@interface ElementType {}

View file

@ -65,10 +65,13 @@ public final class MatroskaExtractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new MatroskaExtractor()};
/**
* Flags controlling the behavior of the extractor.
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_DISABLE_SEEK_FOR_CUES}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES})
@IntDef(
flag = true,
value = {FLAG_DISABLE_SEEK_FOR_CUES})
public @interface Flags {}
/**
* Flag to disable seeking for cues.

View file

@ -47,10 +47,13 @@ public final class Mp3Extractor implements Extractor {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp3Extractor()};
/**
* Flags controlling the behavior of the extractor.
* Flags controlling the behavior of the extractor. Possible flag values are {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA})
@IntDef(
flag = true,
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA})
public @interface Flags {}
/**
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would

View file

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

View file

@ -38,9 +38,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Utility methods for parsing MP4 format atom payloads according to ISO 14496-12.
*/
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
@SuppressWarnings("ConstantField")
/* package */ final class AtomParsers {
/** Thrown if an edit list couldn't be applied. */
@ -619,9 +618,11 @@ import java.util.List;
long timescale = mdhd.readUnsignedInt();
mdhd.skipBytes(version == 0 ? 4 : 8);
int languageCode = mdhd.readUnsignedShort();
String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60)
+ (char) (((languageCode >> 5) & 0x1F) + 0x60)
+ (char) (((languageCode) & 0x1F) + 0x60);
String language =
""
+ (char) (((languageCode >> 10) & 0x1F) + 0x60)
+ (char) (((languageCode >> 5) & 0x1F) + 0x60)
+ (char) ((languageCode & 0x1F) + 0x60);
return Pair.create(timescale, language);
}

View file

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

View file

@ -50,10 +50,13 @@ public final class Mp4Extractor implements Extractor, SeekMap {
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Mp4Extractor()};
/**
* Flags controlling the behavior of the extractor.
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS})
@IntDef(
flag = true,
value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS})
public @interface Flags {}
/**
* Flag to ignore any edit lists in the stream.

View file

@ -28,7 +28,8 @@ import java.lang.annotation.RetentionPolicy;
public final class Track {
/**
* The transformation to apply to samples in the track, if any.
* The transformation to apply to samples in the track, if any. One of {@link
* #TRANSFORMATION_NONE} or {@link #TRANSFORMATION_CEA608_CDAT}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})

View file

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

View file

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

View file

@ -43,7 +43,10 @@ public final class AdtsExtractor implements Extractor {
/** Factory for {@link AdtsExtractor} instances. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AdtsExtractor()};
/** Flags controlling the behavior of the extractor. */
/**
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,

View file

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

View file

@ -34,13 +34,24 @@ import java.util.List;
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {
/**
* Flags controlling elementary stream readers' behavior.
* Flags controlling elementary stream readers' behavior. Possible flag values are {@link
* #FLAG_ALLOW_NON_IDR_KEYFRAMES}, {@link #FLAG_IGNORE_AAC_STREAM}, {@link
* #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link
* #FLAG_IGNORE_SPLICE_INFO_STREAM} and {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM,
FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS})
@IntDef(
flag = true,
value = {
FLAG_ALLOW_NON_IDR_KEYFRAMES,
FLAG_IGNORE_AAC_STREAM,
FLAG_IGNORE_H264_STREAM,
FLAG_DETECT_ACCESS_UNITS,
FLAG_IGNORE_SPLICE_INFO_STREAM,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS
})
public @interface Flags {}
public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1;
public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;

View file

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

View file

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

View file

@ -100,6 +100,8 @@ public final class MpegAudioReader implements ElementaryStreamReader {
case STATE_READING_FRAME:
readFrameRemainder(data);
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.
reader.packetFinished();
break;
default:
throw new IllegalStateException();
}
setState(STATE_READING_HEADER);
}
@ -140,6 +142,8 @@ public final class PesReader implements TsPayloadReader {
}
}
break;
default:
throw new IllegalStateException();
}
}
}

View file

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

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.util.Assertions;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.TimedValueQueue;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
@ -272,10 +273,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final DecoderInputBuffer buffer;
private final DecoderInputBuffer flagsOnlyBuffer;
private final FormatHolder formatHolder;
private final TimedValueQueue<Format> formatQueue;
private final List<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo;
private Format format;
private Format pendingFormat;
private Format outputFormat;
private DrmSession<FrameworkMediaCrypto> drmSession;
private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private MediaCodec codec;
@ -288,12 +292,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
private boolean codecNeedsEosPropagation;
private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers;
private long codecHotswapDeadlineMs;
@ -344,6 +348,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
formatHolder = new FormatHolder();
formatQueue = new TimedValueQueue<>();
decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo();
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
@ -463,10 +468,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecInfo);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
codecNeedsEosPropagation =
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
codecHotswapDeadlineMs =
getState() == STATE_STARTED
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS)
@ -481,11 +487,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true;
}
/**
* Returns whether the codec needs the renderer to propagate the end-of-stream signal directly,
* rather than by using an end-of-stream buffer queued to the codec.
*/
protected boolean getCodecNeedsEosPropagation() {
return false;
}
protected final MediaCodec getCodec() {
return codec;
}
protected final MediaCodecInfo getCodecInfo() {
protected final @Nullable MediaCodecInfo getCodecInfo() {
return codecInfo;
}
@ -501,6 +515,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codec != null) {
flushCodec();
}
formatQueue.clear();
}
@Override
@ -547,11 +562,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false;
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsEosPropagationWorkaround = false;
codecNeedsEosFlushWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false;
codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codecNeedsEosPropagation = false;
codecReceivedEos = false;
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
@ -849,7 +864,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
// We need to re-initialize the codec. Send an end of stream signal to the existing codec so
// that it outputs any remaining buffers before we release it.
if (codecNeedsEosPropagationWorkaround) {
if (codecNeedsEosPropagation) {
// Do nothing.
} else {
codecReceivedEos = true;
@ -917,7 +932,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return false;
}
try {
if (codecNeedsEosPropagationWorkaround) {
if (codecNeedsEosPropagation) {
// Do nothing.
} else {
codecReceivedEos = true;
@ -956,6 +971,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (buffer.isDecodeOnly()) {
decodeOnlyPresentationTimestamps.add(presentationTimeUs);
}
if (pendingFormat != null) {
formatQueue.add(presentationTimeUs, pendingFormat);
pendingFormat = null;
}
buffer.flip();
onQueueInputBuffer(buffer);
@ -1012,6 +1031,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format;
format = newFormat;
pendingFormat = newFormat;
boolean drmInitDataChanged =
!Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData);
@ -1234,42 +1254,48 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
}
if (outputIndex >= 0) {
// We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codec.releaseOutputBuffer(outputIndex, false);
if (outputIndex < 0) {
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) {
processOutputFormat();
return true;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) {
processOutputBuffersChanged();
return true;
} else if (outputBufferInfo.size == 0
&& (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// The dequeued buffer indicates the end of the stream. Process it immediately.
processEndOfStream();
return false;
} else {
this.outputIndex = outputIndex;
outputBuffer = getOutputBuffer(outputIndex);
// The dequeued buffer is a media buffer. Do some initial setup.
// It will be processed by calling processOutputBuffer (possibly multiple times).
if (outputBuffer != null) {
outputBuffer.position(outputBufferInfo.offset);
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
}
shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs);
}
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) {
processOutputFormat();
return true;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) {
processOutputBuffersChanged();
return true;
} else /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ {
if (codecNeedsEosPropagationWorkaround
/* 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.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codec.releaseOutputBuffer(outputIndex, false);
return true;
} else if (outputBufferInfo.size == 0
&& (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// The dequeued buffer indicates the end of the stream. Process it immediately.
processEndOfStream();
return false;
}
this.outputIndex = outputIndex;
outputBuffer = getOutputBuffer(outputIndex);
// The dequeued buffer is a media buffer. Do some initial setup.
// It will be processed by calling processOutputBuffer (possibly multiple times).
if (outputBuffer != null) {
outputBuffer.position(outputBufferInfo.offset);
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
}
shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs);
Format format = formatQueue.pollFloor(outputBufferInfo.presentationTimeUs);
if (format != null) {
outputFormat = format;
}
}
boolean processedOutputBuffer;
@ -1284,7 +1310,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputIndex,
outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer);
shouldSkipOutputBuffer,
outputFormat);
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
@ -1303,7 +1330,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputIndex,
outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer);
shouldSkipOutputBuffer,
outputFormat);
}
if (processedOutputBuffer) {
@ -1348,36 +1376,43 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* 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
* true is returned then the next call to this method will receive a new buffer to be processed.
* If false is returned then the same buffer will be passed to the next call. An implementation of
* this method is free to modify the buffer and can assume that the buffer will not be externally
* modified between successive calls. Hence an implementation can, for example, modify the
* buffer's position to keep track of how much of the data it has processed.
* <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
* current iteration of the rendering loop.
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
* measured at the start of the current iteration of the rendering loop.
* <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 current
* iteration of the rendering loop.
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
* start of the current iteration of the rendering loop.
* @param codec The {@link MediaCodec} instance.
* @param buffer The output buffer to process.
* @param bufferIndex The index of the output buffer.
* @param bufferFlags The flags attached to the output buffer.
* @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds.
* @param shouldSkip Whether the buffer should be skipped (i.e. not rendered).
*
* @param format The format associated with the buffer.
* @return Whether the output buffer was fully processed (e.g. rendered or skipped).
* @throws ExoPlaybackException If an error occurs processing the output buffer.
*/
protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs,
MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags,
long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException;
protected abstract boolean processOutputBuffer(
long positionUs,
long elapsedRealtimeUs,
MediaCodec codec,
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
long bufferPresentationTimeUs,
boolean shouldSkip,
Format format)
throws ExoPlaybackException;
/**
* Incrementally renders any remaining output.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -336,12 +336,7 @@ public final class DownloadManager {
tasks.get(i).stop();
}
final ConditionVariable fileIOFinishedCondition = new ConditionVariable();
fileIOHandler.post(new Runnable() {
@Override
public void run() {
fileIOFinishedCondition.open();
}
});
fileIOHandler.post(fileIOFinishedCondition::open);
fileIOFinishedCondition.block();
fileIOThread.quit();
logd("Released");
@ -451,51 +446,45 @@ public final class DownloadManager {
private void loadActions() {
fileIOHandler.post(
new Runnable() {
@Override
public void run() {
DownloadAction[] loadedActions;
try {
loadedActions = actionFile.load(DownloadManager.this.deserializers);
logd("Action file is loaded.");
} catch (Throwable e) {
Log.e(TAG, "Action file loading failed.", e);
loadedActions = new DownloadAction[0];
}
final DownloadAction[] actions = loadedActions;
handler.post(
new Runnable() {
@Override
public void run() {
if (released) {
return;
}
List<Task> pendingTasks = new ArrayList<>(tasks);
tasks.clear();
for (DownloadAction action : actions) {
addTaskForAction(action);
}
logd("Tasks are created.");
initialized = true;
for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this);
}
if (!pendingTasks.isEmpty()) {
tasks.addAll(pendingTasks);
saveActions();
}
maybeStartTasks();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.currentState == STATE_QUEUED) {
// Task did not change out of its initial state, and so its initial state
// won't have been reported to listeners. Do so now.
notifyListenersTaskStateChange(task);
}
}
}
});
() -> {
DownloadAction[] loadedActions;
try {
loadedActions = actionFile.load(DownloadManager.this.deserializers);
logd("Action file is loaded.");
} catch (Throwable e) {
Log.e(TAG, "Action file loading failed.", e);
loadedActions = new DownloadAction[0];
}
final DownloadAction[] actions = loadedActions;
handler.post(
() -> {
if (released) {
return;
}
List<Task> pendingTasks = new ArrayList<>(tasks);
tasks.clear();
for (DownloadAction action : actions) {
addTaskForAction(action);
}
logd("Tasks are created.");
initialized = true;
for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this);
}
if (!pendingTasks.isEmpty()) {
tasks.addAll(pendingTasks);
saveActions();
}
maybeStartTasks();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.currentState == STATE_QUEUED) {
// Task did not change out of its initial state, and so its initial state
// won't have been reported to listeners. Do so now.
notifyListenersTaskStateChange(task);
}
}
});
});
}
@ -507,17 +496,15 @@ public final class DownloadManager {
for (int i = 0; i < tasks.size(); i++) {
actions[i] = tasks.get(i).action;
}
fileIOHandler.post(new Runnable() {
@Override
public void run() {
try {
actionFile.store(actions);
logd("Actions persisted.");
} catch (IOException e) {
Log.e(TAG, "Persisting actions failed.", e);
}
}
});
fileIOHandler.post(
() -> {
try {
actionFile.store(actions);
logd("Actions persisted.");
} catch (IOException e) {
Log.e(TAG, "Persisting actions failed.", e);
}
});
}
private static void logd(String message) {
@ -534,7 +521,8 @@ public final class DownloadManager {
public static final class TaskState {
/**
* Task states.
* Task states. One of {@link #STATE_QUEUED}, {@link #STATE_STARTED}, {@link #STATE_COMPLETED},
* {@link #STATE_CANCELED} or {@link #STATE_FAILED}.
*
* <p>Transition diagram:
*
@ -614,7 +602,10 @@ public final class DownloadManager {
private static final class Task implements Runnable {
/**
* Task states.
* Task states. One of {@link TaskState#STATE_QUEUED}, {@link TaskState#STATE_STARTED}, {@link
* TaskState#STATE_COMPLETED}, {@link TaskState#STATE_CANCELED}, {@link TaskState#STATE_FAILED},
* {@link #STATE_QUEUED_CANCELING}, {@link #STATE_STARTED_CANCELING} or {@link
* #STATE_STARTED_STOPPING}.
*
* <p>Transition map (vertical states are source states):
*
@ -771,12 +762,7 @@ public final class DownloadManager {
private void cancel() {
if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) {
downloadManager.handler.post(
new Runnable() {
@Override
public void run() {
changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED);
}
});
() -> changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED));
} else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) {
cancelDownload();
}
@ -851,19 +837,14 @@ public final class DownloadManager {
}
final Throwable finalError = error;
downloadManager.handler.post(
new Runnable() {
@Override
public void run() {
if (changeStateAndNotify(
STATE_STARTED,
finalError != null ? STATE_FAILED : STATE_COMPLETED,
finalError)
|| changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED)
|| changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) {
return;
}
throw new IllegalStateException();
() -> {
if (changeStateAndNotify(
STATE_STARTED, finalError != null ? STATE_FAILED : STATE_COMPLETED, finalError)
|| changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED)
|| changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) {
return;
}
throw new IllegalStateException();
});
}

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