mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
Merge branch 'dev-v2' of https://github.com/google/ExoPlayer into dev-v2
This commit is contained in:
commit
ebb027031e
260 changed files with 5166 additions and 3457 deletions
|
|
@ -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 ###
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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])) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ dependencies {
|
|||
testImplementation project(modulePrefix + 'testutils-robolectric')
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
|
||||
|
||||
ext {
|
||||
javadocTitle = 'IMA extension'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("ConstantField")
|
||||
/* package*/ abstract class Atom {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ public final class DtsReader implements ElementaryStreamReader {
|
|||
state = STATE_FINDING_SYNC;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
|||
case STATE_READING_FRAME:
|
||||
readFrameRemainder(data);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
|
|||
sectionHeader = new ParsableBitArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ByteBufferBackingArray")
|
||||
@Override
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
// Internal timestamps adjustment.
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue