mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
commit
f8a8302f7b
13
README.md
13
README.md
@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates.
|
||||
|
||||
#### Via jCenter ####
|
||||
|
||||
The easiest way to get started using ExoPlayer is by including the following in
|
||||
your project's `build.gradle` file:
|
||||
The easiest way to get started using ExoPlayer is to add it as a gradle
|
||||
dependency. You need to make sure you have the jcenter repository included in
|
||||
the `build.gradle` file in the root of your project:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
```
|
||||
|
||||
Next, include the following in your module's `build.gradle` file:
|
||||
|
||||
```gradle
|
||||
compile 'com.google.android.exoplayer:exoplayer:rX.X.X'
|
||||
|
@ -1,5 +1,14 @@
|
||||
# Release notes #
|
||||
|
||||
### r2.0.1 ###
|
||||
|
||||
* Fix playback of short duration content
|
||||
([#1837](https://github.com/google/ExoPlayer/issues/1837)).
|
||||
* Fix MergingMediaSource preparation issue
|
||||
([#1853](https://github.com/google/ExoPlayer/issues/1853)).
|
||||
* Fix live stream buffering (out of memory) issue
|
||||
([#1825](https://github.com/google/ExoPlayer/issues/1825)).
|
||||
|
||||
### r2.0.0 ###
|
||||
|
||||
ExoPlayer 2.x is a major iteration of the library. It includes significant API
|
||||
|
@ -39,14 +39,14 @@ android {
|
||||
|
||||
productFlavors {
|
||||
demo
|
||||
demo_ext
|
||||
demoExt
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
demo_extCompile project(path: ':extension-ffmpeg')
|
||||
demo_extCompile project(path: ':extension-flac')
|
||||
demo_extCompile project(path: ':extension-opus')
|
||||
demo_extCompile project(path: ':extension-vp9')
|
||||
demoExtCompile project(path: ':extension-ffmpeg')
|
||||
demoExtCompile project(path: ':extension-flac')
|
||||
demoExtCompile project(path: ':extension-opus')
|
||||
demoExtCompile project(path: ':extension-vp9')
|
||||
}
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer2.demo"
|
||||
android:versionCode="2000"
|
||||
android:versionName="2.0.0">
|
||||
android:versionCode="2001"
|
||||
android:versionName="2.0.1">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
@ -16,9 +16,33 @@
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
import android.app.Application;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* Placeholder application to facilitate overriding Application methods for debugging and testing.
|
||||
*/
|
||||
public class DemoApplication extends Application {
|
||||
|
||||
protected String userAgent;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
||||
}
|
||||
|
||||
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||
return new DefaultDataSourceFactory(this, bandwidthMeter,
|
||||
buildHttpDataSourceFactory(bandwidthMeter));
|
||||
}
|
||||
|
||||
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import java.io.IOException;
|
||||
@ -54,7 +55,7 @@ import java.util.Locale;
|
||||
/* package */ final class EventLogger implements ExoPlayer.EventListener,
|
||||
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
|
||||
ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener,
|
||||
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> {
|
||||
TrackSelector.EventListener<MappedTrackInfo>, MetadataRenderer.Output<List<Id3Frame>> {
|
||||
|
||||
private static final String TAG = "EventLogger";
|
||||
private static final int MAX_TIMELINE_ITEM_LINES = 3;
|
||||
@ -125,23 +126,24 @@ import java.util.Locale;
|
||||
// MappingTrackSelector.EventListener
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackInfo trackInfo) {
|
||||
public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
|
||||
Log.d(TAG, "Tracks [");
|
||||
// Log tracks associated to renderers.
|
||||
for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) {
|
||||
TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex);
|
||||
TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex);
|
||||
MappedTrackInfo info = trackSelections.info;
|
||||
for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) {
|
||||
TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex);
|
||||
TrackSelection trackSelection = trackSelections.get(rendererIndex);
|
||||
if (trackGroups.length > 0) {
|
||||
Log.d(TAG, " Renderer:" + rendererIndex + " [");
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = trackGroups.get(groupIndex);
|
||||
String adaptiveSupport = getAdaptiveSupportString(
|
||||
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
|
||||
trackGroup.length, info.getAdaptiveSupport(rendererIndex, groupIndex, false));
|
||||
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
|
||||
String formatSupport = getFormatSupportString(
|
||||
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
|
||||
info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
|
||||
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
|
||||
+ getFormatString(trackGroup.getFormat(trackIndex))
|
||||
+ ", supported=" + formatSupport);
|
||||
@ -152,7 +154,7 @@ import java.util.Locale;
|
||||
}
|
||||
}
|
||||
// Log tracks not associated with a renderer.
|
||||
TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups();
|
||||
TrackGroupArray trackGroups = info.getUnassociatedTrackGroups();
|
||||
if (trackGroups.length > 0) {
|
||||
Log.d(TAG, " Renderer:None [");
|
||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||
|
@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||
@ -55,15 +56,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.ui.DebugTextViewHelper;
|
||||
import com.google.android.exoplayer2.ui.PlaybackControlView;
|
||||
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.net.CookieHandler;
|
||||
@ -77,7 +78,7 @@ import java.util.UUID;
|
||||
* An activity that plays media using {@link SimpleExoPlayer}.
|
||||
*/
|
||||
public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener,
|
||||
MappingTrackSelector.EventListener, PlaybackControlView.VisibilityListener {
|
||||
TrackSelector.EventListener<MappedTrackInfo>, PlaybackControlView.VisibilityListener {
|
||||
|
||||
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
||||
public static final String DRM_LICENSE_URL = "drm_license_url";
|
||||
@ -106,7 +107,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
private TextView debugTextView;
|
||||
private Button retryButton;
|
||||
|
||||
private String userAgent;
|
||||
private DataSource.Factory mediaDataSourceFactory;
|
||||
private SimpleExoPlayer player;
|
||||
private MappingTrackSelector trackSelector;
|
||||
@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
shouldAutoPlay = true;
|
||||
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
||||
mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||
mainHandler = new Handler();
|
||||
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
||||
@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
initializePlayer();
|
||||
} else if (view.getParent() == debugRootView) {
|
||||
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
|
||||
trackSelector.getTrackInfo(), (int) view.getTag());
|
||||
trackSelector.getCurrentSelections().info, (int) view.getTag());
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +221,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
||||
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
||||
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
|
||||
DrmSessionManager drmSessionManager = null;
|
||||
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||
if (drmSchemeUuid != null) {
|
||||
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
|
||||
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
|
||||
@ -316,15 +315,15 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||
: uri.getLastPathSegment());
|
||||
switch (type) {
|
||||
case Util.TYPE_SS:
|
||||
case C.TYPE_SS:
|
||||
return new SsMediaSource(uri, buildDataSourceFactory(false),
|
||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
||||
case Util.TYPE_DASH:
|
||||
case C.TYPE_DASH:
|
||||
return new DashMediaSource(uri, buildDataSourceFactory(false),
|
||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
||||
case Util.TYPE_HLS:
|
||||
case C.TYPE_HLS:
|
||||
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
|
||||
case Util.TYPE_OTHER:
|
||||
case C.TYPE_OTHER:
|
||||
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
|
||||
mainHandler, eventLogger);
|
||||
default: {
|
||||
@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
}
|
||||
}
|
||||
|
||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl,
|
||||
Map<String, String> keyRequestProperties)
|
||||
throws UnsupportedDrmException {
|
||||
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
|
||||
String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
|
||||
if (Util.SDK_INT < 18) {
|
||||
return null;
|
||||
}
|
||||
@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
* @return A new DataSource factory.
|
||||
*/
|
||||
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
||||
return new DefaultDataSourceFactory(this, useBandwidthMeter ? BANDWIDTH_METER : null,
|
||||
buildHttpDataSourceFactory(useBandwidthMeter));
|
||||
return ((DemoApplication) getApplication())
|
||||
.buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
* @return A new HttpDataSource factory.
|
||||
*/
|
||||
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
||||
return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
return ((DemoApplication) getApplication())
|
||||
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
}
|
||||
|
||||
// ExoPlayer.EventListener implementation
|
||||
@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
// MappingTrackSelector.EventListener implementation
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackInfo trackInfo) {
|
||||
public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
|
||||
updateButtonVisibilities();
|
||||
MappedTrackInfo trackInfo = trackSelections.info;
|
||||
if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) {
|
||||
showToast(R.string.error_unsupported_video);
|
||||
}
|
||||
@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
return;
|
||||
}
|
||||
|
||||
TrackInfo trackInfo = trackSelector.getTrackInfo();
|
||||
if (trackInfo == null) {
|
||||
TrackSelections<MappedTrackInfo> trackSelections = trackSelector.getCurrentSelections();
|
||||
if (trackSelections == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rendererCount = trackInfo.rendererCount;
|
||||
int rendererCount = trackSelections.length;
|
||||
for (int i = 0; i < rendererCount; i++) {
|
||||
TrackGroupArray trackGroups = trackInfo.getTrackGroups(i);
|
||||
TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i);
|
||||
if (trackGroups.length != 0) {
|
||||
Button button = new Button(this);
|
||||
int label;
|
||||
|
@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
@ -51,7 +51,7 @@ import java.util.Locale;
|
||||
private final MappingTrackSelector selector;
|
||||
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
||||
|
||||
private TrackInfo trackInfo;
|
||||
private MappedTrackInfo trackInfo;
|
||||
private int rendererIndex;
|
||||
private TrackGroupArray trackGroups;
|
||||
private boolean[] trackGroupsAdaptive;
|
||||
@ -82,7 +82,7 @@ import java.util.Locale;
|
||||
* @param trackInfo The current track information.
|
||||
* @param rendererIndex The index of the renderer.
|
||||
*/
|
||||
public void showSelectionDialog(Activity activity, CharSequence title, TrackInfo trackInfo,
|
||||
public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo,
|
||||
int rendererIndex) {
|
||||
this.trackInfo = trackInfo;
|
||||
this.rendererIndex = rendererIndex;
|
||||
@ -203,11 +203,7 @@ import java.util.Locale;
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (isDisabled) {
|
||||
selector.setRendererDisabled(rendererIndex, true);
|
||||
return;
|
||||
}
|
||||
selector.setRendererDisabled(rendererIndex, false);
|
||||
selector.setRendererDisabled(rendererIndex, isDisabled);
|
||||
if (override != null) {
|
||||
selector.setSelectionOverride(rendererIndex, trackGroups, override);
|
||||
} else {
|
||||
|
@ -100,7 +100,8 @@ public final class CronetDataSourceTest {
|
||||
Executor executor, int priority,
|
||||
Collection<Object> connectionAnnotations,
|
||||
boolean disableCache,
|
||||
boolean disableConnectionMigration);
|
||||
boolean disableConnectionMigration,
|
||||
boolean allowDirectExecutor);
|
||||
}
|
||||
|
||||
@Mock
|
||||
@ -108,7 +109,7 @@ public final class CronetDataSourceTest {
|
||||
@Mock
|
||||
private Predicate<String> mockContentTypePredicate;
|
||||
@Mock
|
||||
private TransferListener mockTransferListener;
|
||||
private TransferListener<CronetDataSource> mockTransferListener;
|
||||
@Mock
|
||||
private Clock mockClock;
|
||||
@Mock
|
||||
@ -143,6 +144,7 @@ public final class CronetDataSourceTest {
|
||||
anyInt(),
|
||||
eq(Collections.emptyList()),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class))).thenReturn(mockUrlRequest);
|
||||
mockStatusResponse();
|
||||
|
||||
@ -170,8 +172,8 @@ public final class CronetDataSourceTest {
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOpeningTwiceThrows() throws HttpDataSourceException, IllegalStateException {
|
||||
mockResponesStartSuccess();
|
||||
public void testOpeningTwiceThrows() throws HttpDataSourceException {
|
||||
mockResponseStartSuccess();
|
||||
|
||||
assertConnectionState(CronetDataSource.IDLE_CONNECTION);
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
@ -181,7 +183,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
dataSourceUnderTest.close();
|
||||
@ -194,6 +196,7 @@ public final class CronetDataSourceTest {
|
||||
anyInt(),
|
||||
eq(Collections.emptyList()),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class))).thenReturn(mockUrlRequest2);
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
@ -214,7 +217,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestStartCalled() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
verify(mockCronetEngine).createRequest(
|
||||
@ -224,13 +227,14 @@ public final class CronetDataSourceTest {
|
||||
anyInt(),
|
||||
eq(Collections.emptyList()),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class),
|
||||
any(Boolean.class));
|
||||
verify(mockUrlRequest).start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestHeadersSet() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||
testResponseHeader.put("Content-Length", Long.toString(5000L));
|
||||
@ -248,13 +252,29 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestOpen() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec));
|
||||
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
|
||||
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRequestOpenGzippedCompressedReturnsDataSpecLength()
|
||||
throws HttpDataSourceException {
|
||||
testResponseHeader.put("Content-Encoding", "gzip");
|
||||
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
|
||||
mockResponseStartSuccess();
|
||||
|
||||
// Data spec's requested length, 5000. Test response's length, 16,000.
|
||||
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||
|
||||
assertEquals(5000 /* contentLength */, dataSourceUnderTest.open(testDataSpec));
|
||||
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
|
||||
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestOpenFail() {
|
||||
mockResponseStartFailure();
|
||||
@ -291,7 +311,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestOpenValidatesStatusCode() {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
|
||||
|
||||
try {
|
||||
@ -308,7 +328,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestOpenValidatesContentTypePredicate() {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false);
|
||||
|
||||
try {
|
||||
@ -325,7 +345,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestOpenValidatesContentLength() {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
// Data spec's requested length, 5000. Test response's length, 16,000.
|
||||
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||
@ -344,7 +364,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testPostRequestOpen() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
|
||||
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec));
|
||||
@ -354,7 +374,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testPostRequestOpenValidatesContentType() {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
try {
|
||||
dataSourceUnderTest.open(testPostDataSpec);
|
||||
@ -366,7 +386,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testPostRequestOpenRejects307Redirects() {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockResponseStartRedirect();
|
||||
|
||||
try {
|
||||
@ -380,7 +400,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testRequestReadTwice() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
@ -402,7 +422,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testSecondRequestNoContentLength() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
byte[] returnedBuffer = new byte[8];
|
||||
@ -433,7 +453,23 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testReadWithOffset() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
|
||||
byte[] returnedBuffer = new byte[16];
|
||||
int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8);
|
||||
assertArrayEquals(prefixZeros(buildTestDataArray(0, 8), 16), returnedBuffer);
|
||||
assertEquals(8, bytesRead);
|
||||
verify(mockTransferListener).onBytesTransferred(dataSourceUnderTest, 8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWithUnsetLength() throws HttpDataSourceException {
|
||||
testResponseHeader.remove("Content-Length");
|
||||
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
@ -447,7 +483,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testReadReturnsWhatItCan() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
@ -461,7 +497,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testClosedMeansClosed() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
int bytesRead = 0;
|
||||
@ -489,7 +525,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testOverread() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadSuccess();
|
||||
|
||||
// Ask for 16 bytes
|
||||
@ -676,7 +712,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionFromTransferListener() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
|
||||
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
|
||||
// the subsequent open() call succeeds.
|
||||
@ -695,7 +731,7 @@ public final class CronetDataSourceTest {
|
||||
|
||||
@Test
|
||||
public void testReadFailure() throws HttpDataSourceException {
|
||||
mockResponesStartSuccess();
|
||||
mockResponseStartSuccess();
|
||||
mockReadFailure();
|
||||
|
||||
dataSourceUnderTest.open(testDataSpec);
|
||||
@ -722,7 +758,7 @@ public final class CronetDataSourceTest {
|
||||
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
|
||||
}
|
||||
|
||||
private void mockResponesStartSuccess() {
|
||||
private void mockResponseStartSuccess() {
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
|
@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
||||
try {
|
||||
validateResponse(info);
|
||||
responseInfo = info;
|
||||
// Check content length.
|
||||
contentLength = getContentLength(info.getAllHeaders());
|
||||
// If a specific length is requested and a specific length is returned but the 2 don't match
|
||||
// it's an error.
|
||||
if (currentDataSpec.length != C.LENGTH_UNSET
|
||||
&& contentLength != C.LENGTH_UNSET
|
||||
&& currentDataSpec.length != contentLength) {
|
||||
throw new OpenException("Content length did not match requested length", currentDataSpec,
|
||||
getCurrentRequestStatus());
|
||||
|
||||
if (isCompressed(info)) {
|
||||
contentLength = currentDataSpec.length;
|
||||
} else {
|
||||
// Check content length.
|
||||
contentLength = getContentLength(info.getAllHeaders());
|
||||
// If a specific length is requested and a specific length is returned but the 2 don't match
|
||||
// it's an error.
|
||||
if (currentDataSpec.length != C.LENGTH_UNSET
|
||||
&& contentLength != C.LENGTH_UNSET
|
||||
&& currentDataSpec.length != contentLength) {
|
||||
throw new OpenException("Content length did not match requested length", currentDataSpec,
|
||||
getCurrentRequestStatus());
|
||||
}
|
||||
}
|
||||
|
||||
if (contentLength > 0) {
|
||||
@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} iff the content is compressed.
|
||||
*
|
||||
* <p>If {@code true}, clients cannot use the value of content length from the request headers to
|
||||
* read the data, since Cronet returns the uncompressed data and this content length reflects the
|
||||
* compressed content length.
|
||||
*/
|
||||
private boolean isCompressed(UrlResponseInfo info) {
|
||||
for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) {
|
||||
if (entry.getKey().equalsIgnoreCase("Content-Encoding")) {
|
||||
return !entry.getValue().equalsIgnoreCase("identity");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
|
||||
// Check for a valid response code.
|
||||
int responseCode = info.getHttpStatusCode();
|
||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
||||
player.addListener(this);
|
||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer();
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
||||
player.addListener(this);
|
||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0);
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||
player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector);
|
||||
player.addListener(this);
|
||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||
|
@ -55,6 +55,7 @@ dependencies {
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
||||
compile 'com.android.support:support-annotations:24.2.0'
|
||||
}
|
||||
|
||||
android.libraryVariants.all { variant ->
|
||||
@ -95,7 +96,7 @@ publish {
|
||||
userOrg = 'google'
|
||||
groupId = 'com.google.android.exoplayer'
|
||||
artifactId = 'exoplayer'
|
||||
version = 'r2.0.0'
|
||||
version = 'r2.0.1'
|
||||
description = 'The ExoPlayer library.'
|
||||
website = 'https://github.com/google/ExoPlayer'
|
||||
}
|
||||
|
BIN
library/src/androidTest/assets/mp3/play-trimmed.mp3
Normal file
BIN
library/src/androidTest/assets/mp3/play-trimmed.mp3
Normal file
Binary file not shown.
33
library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump
Normal file
33
library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump
Normal file
@ -0,0 +1,33 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = -1
|
||||
pixelWidthHeightRatio = -1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = -1
|
||||
encoderPadding = -1
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 1
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 418, hash B819987
|
||||
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump
Normal file
@ -0,0 +1,29 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = -1
|
||||
pixelWidthHeightRatio = -1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = -1
|
||||
encoderPadding = -1
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 0
|
||||
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump
Normal file
@ -0,0 +1,29 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = -1
|
||||
pixelWidthHeightRatio = -1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = -1
|
||||
encoderPadding = -1
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 0
|
||||
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump
Normal file
@ -0,0 +1,29 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = -1
|
||||
pixelWidthHeightRatio = -1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = -1
|
||||
encoderPadding = -1
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 0
|
||||
tracksEnded = true
|
@ -0,0 +1,33 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = -1
|
||||
pixelWidthHeightRatio = -1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44100
|
||||
pcmEncoding = -1
|
||||
encoderDelay = -1
|
||||
encoderPadding = -1
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 1
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 418, hash B819987
|
||||
tracksEnded = true
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = application/eia-608
|
||||
sampleMimeType = application/cea-608
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
@ -25,305 +25,605 @@ track 0:
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
sample count = 75
|
||||
sample count = 150
|
||||
sample 0:
|
||||
time = 37657512133
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
data = length 3, hash 7363
|
||||
sample 1:
|
||||
time = 37657528822
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 2:
|
||||
time = 37657545511
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6CD
|
||||
sample 2:
|
||||
data = length 3, hash 766F
|
||||
sample 3:
|
||||
time = 37657562177
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 4:
|
||||
time = 37657578866
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6DC
|
||||
sample 3:
|
||||
data = length 3, hash 767E
|
||||
sample 5:
|
||||
time = 37657595555
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 6:
|
||||
time = 37657612244
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF65B
|
||||
sample 4:
|
||||
data = length 15, hash E4359178
|
||||
sample 7:
|
||||
time = 37657628911
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 8:
|
||||
time = 37657645600
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6CD
|
||||
sample 5:
|
||||
data = length 12, hash 15EBEB66
|
||||
sample 9:
|
||||
time = 37657662288
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 10:
|
||||
time = 37657678977
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF67B
|
||||
sample 6:
|
||||
data = length 3, hash 761D
|
||||
sample 11:
|
||||
time = 37657695644
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 12:
|
||||
time = 37657712333
|
||||
flags = 1
|
||||
data = length 2, hash 2B5
|
||||
sample 7:
|
||||
data = length 30, hash E181418F
|
||||
sample 13:
|
||||
time = 37657729022
|
||||
flags = 1
|
||||
data = length 6, hash 36289CE2
|
||||
sample 14:
|
||||
time = 37657745711
|
||||
flags = 1
|
||||
data = length 2, hash F5
|
||||
sample 8:
|
||||
data = length 12, hash 3C304F5B
|
||||
sample 15:
|
||||
time = 37657762377
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 16:
|
||||
time = 37657779066
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF87A
|
||||
sample 9:
|
||||
data = length 12, hash 88DD8EF6
|
||||
sample 17:
|
||||
time = 37657795755
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 18:
|
||||
time = 37657812444
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF698
|
||||
sample 10:
|
||||
data = length 12, hash 8B411833
|
||||
sample 19:
|
||||
time = 37657829111
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 20:
|
||||
time = 37657845800
|
||||
flags = 1
|
||||
data = length 2, hash 1F4
|
||||
sample 11:
|
||||
data = length 12, hash 742A2DF1
|
||||
sample 21:
|
||||
time = 37657862488
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 22:
|
||||
time = 37657879177
|
||||
flags = 1
|
||||
data = length 2, hash 803
|
||||
sample 12:
|
||||
data = length 12, hash 9A2ECBEE
|
||||
sample 23:
|
||||
time = 37657895844
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 24:
|
||||
time = 37657912533
|
||||
flags = 1
|
||||
data = length 2, hash 1F8
|
||||
sample 13:
|
||||
data = length 12, hash 562688EA
|
||||
sample 25:
|
||||
time = 37657929222
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 26:
|
||||
time = 37657945911
|
||||
flags = 1
|
||||
data = length 2, hash 117A
|
||||
sample 14:
|
||||
data = length 12, hash ADE4B953
|
||||
sample 27:
|
||||
time = 37657962577
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 28:
|
||||
time = 37657979266
|
||||
flags = 1
|
||||
data = length 2, hash 166
|
||||
sample 15:
|
||||
data = length 12, hash F927E3E5
|
||||
sample 29:
|
||||
time = 37657995955
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 30:
|
||||
time = 37658012644
|
||||
flags = 1
|
||||
data = length 2, hash 105A
|
||||
sample 16:
|
||||
data = length 12, hash EA327945
|
||||
sample 31:
|
||||
time = 37658029311
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 32:
|
||||
time = 37658046000
|
||||
flags = 1
|
||||
data = length 2, hash FCF
|
||||
sample 17:
|
||||
data = length 12, hash 3E5DA13C
|
||||
sample 33:
|
||||
time = 37658062688
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 34:
|
||||
time = 37658079377
|
||||
flags = 1
|
||||
data = length 2, hash 1253
|
||||
sample 18:
|
||||
data = length 12, hash BF646AE3
|
||||
sample 35:
|
||||
time = 37658096044
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 36:
|
||||
time = 37658112733
|
||||
flags = 1
|
||||
data = length 2, hash 11DA
|
||||
sample 19:
|
||||
data = length 12, hash 41E3BA78
|
||||
sample 37:
|
||||
time = 37658129422
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 38:
|
||||
time = 37658146111
|
||||
flags = 1
|
||||
data = length 2, hash 795
|
||||
sample 20:
|
||||
data = length 12, hash A2945EF6
|
||||
sample 39:
|
||||
time = 37658162777
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 40:
|
||||
time = 37658179466
|
||||
flags = 1
|
||||
data = length 2, hash 103E
|
||||
sample 21:
|
||||
data = length 12, hash 26735812
|
||||
sample 41:
|
||||
time = 37658196155
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 42:
|
||||
time = 37658212844
|
||||
flags = 1
|
||||
data = length 2, hash 120F
|
||||
sample 22:
|
||||
data = length 12, hash DC14D3D8
|
||||
sample 43:
|
||||
time = 37658229511
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 44:
|
||||
time = 37658246200
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF698
|
||||
sample 23:
|
||||
data = length 12, hash 882191BE
|
||||
sample 45:
|
||||
time = 37658262888
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 46:
|
||||
time = 37658279577
|
||||
flags = 1
|
||||
data = length 2, hash 1F4
|
||||
sample 24:
|
||||
data = length 12, hash 8B4886B1
|
||||
sample 47:
|
||||
time = 37658296244
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 48:
|
||||
time = 37658312933
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF71B
|
||||
sample 25:
|
||||
data = length 12, hash 98D98F96
|
||||
sample 49:
|
||||
time = 37658329622
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 50:
|
||||
time = 37658346311
|
||||
flags = 1
|
||||
data = length 2, hash F91
|
||||
sample 26:
|
||||
data = length 30, hash CF8E53E3
|
||||
sample 51:
|
||||
time = 37658362977
|
||||
flags = 1
|
||||
data = length 6, hash 36289CE2
|
||||
sample 52:
|
||||
time = 37658379666
|
||||
flags = 1
|
||||
data = length 2, hash 166
|
||||
sample 27:
|
||||
data = length 12, hash F883C9EE
|
||||
sample 53:
|
||||
time = 37658396355
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 54:
|
||||
time = 37658413044
|
||||
flags = 1
|
||||
data = length 2, hash 1023
|
||||
sample 28:
|
||||
data = length 12, hash 6E6B2B9C
|
||||
sample 55:
|
||||
time = 37658429711
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 56:
|
||||
time = 37658446400
|
||||
flags = 1
|
||||
data = length 2, hash 117A
|
||||
sample 29:
|
||||
data = length 12, hash B4FE7F08
|
||||
sample 57:
|
||||
time = 37658463088
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 58:
|
||||
time = 37658479777
|
||||
flags = 1
|
||||
data = length 2, hash 784
|
||||
sample 30:
|
||||
data = length 12, hash 5A1EA7C7
|
||||
sample 59:
|
||||
time = 37658496444
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 60:
|
||||
time = 37658513133
|
||||
flags = 1
|
||||
data = length 2, hash 1F8
|
||||
sample 31:
|
||||
data = length 12, hash 46BD6CC9
|
||||
sample 61:
|
||||
time = 37658529822
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 62:
|
||||
time = 37658546511
|
||||
flags = 1
|
||||
data = length 2, hash 10D9
|
||||
sample 32:
|
||||
data = length 12, hash 1B1E2554
|
||||
sample 63:
|
||||
time = 37658563177
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 64:
|
||||
time = 37658579866
|
||||
flags = 1
|
||||
data = length 2, hash 935
|
||||
sample 33:
|
||||
data = length 12, hash 91FCC537
|
||||
sample 65:
|
||||
time = 37658596555
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 66:
|
||||
time = 37658613244
|
||||
flags = 1
|
||||
data = length 2, hash 2B5
|
||||
sample 34:
|
||||
data = length 12, hash A9355E1B
|
||||
sample 67:
|
||||
time = 37658629911
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 68:
|
||||
time = 37658646600
|
||||
flags = 1
|
||||
data = length 2, hash F5
|
||||
sample 35:
|
||||
data = length 12, hash 2511F69B
|
||||
sample 69:
|
||||
time = 37658663288
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 70:
|
||||
time = 37658679977
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF87A
|
||||
sample 36:
|
||||
data = length 12, hash 90925736
|
||||
sample 71:
|
||||
time = 37658696644
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 72:
|
||||
time = 37658713333
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF698
|
||||
sample 37:
|
||||
data = length 21, hash 431EEE30
|
||||
sample 73:
|
||||
time = 37658730022
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 74:
|
||||
time = 37658746711
|
||||
flags = 1
|
||||
data = length 2, hash 1F4
|
||||
sample 38:
|
||||
data = length 12, hash 7BDEF631
|
||||
sample 75:
|
||||
time = 37658763377
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 76:
|
||||
time = 37658780066
|
||||
flags = 1
|
||||
data = length 2, hash 793
|
||||
sample 39:
|
||||
data = length 12, hash A2EEF59E
|
||||
sample 77:
|
||||
time = 37658796755
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 78:
|
||||
time = 37658813444
|
||||
flags = 1
|
||||
data = length 2, hash FF0
|
||||
sample 40:
|
||||
data = length 12, hash BFC6C022
|
||||
sample 79:
|
||||
time = 37658830111
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 80:
|
||||
time = 37658846800
|
||||
flags = 1
|
||||
data = length 2, hash 16B
|
||||
sample 41:
|
||||
data = length 12, hash CD4D8FCA
|
||||
sample 81:
|
||||
time = 37658863488
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 82:
|
||||
time = 37658880177
|
||||
flags = 1
|
||||
data = length 2, hash 2C0
|
||||
sample 42:
|
||||
data = length 12, hash 2BDE8EFA
|
||||
sample 83:
|
||||
time = 37658896844
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 84:
|
||||
time = 37658913533
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF953
|
||||
sample 43:
|
||||
data = length 12, hash 8C858812
|
||||
sample 85:
|
||||
time = 37658930222
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 86:
|
||||
time = 37658946911
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 44:
|
||||
data = length 12, hash DE7D0E31
|
||||
sample 87:
|
||||
time = 37658963577
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 88:
|
||||
time = 37658980266
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 45:
|
||||
data = length 3, hash 7363
|
||||
sample 89:
|
||||
time = 37658996955
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 90:
|
||||
time = 37659013644
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 46:
|
||||
data = length 3, hash 7363
|
||||
sample 91:
|
||||
time = 37659030311
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 92:
|
||||
time = 37659047000
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 47:
|
||||
data = length 3, hash 7363
|
||||
sample 93:
|
||||
time = 37659063688
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 94:
|
||||
time = 37659080377
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 48:
|
||||
data = length 3, hash 7363
|
||||
sample 95:
|
||||
time = 37659097044
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 96:
|
||||
time = 37659113733
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 49:
|
||||
data = length 3, hash 7363
|
||||
sample 97:
|
||||
time = 37659130422
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 98:
|
||||
time = 37659147111
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 50:
|
||||
data = length 3, hash 7363
|
||||
sample 99:
|
||||
time = 37659163777
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 100:
|
||||
time = 37659180466
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 51:
|
||||
data = length 3, hash 7363
|
||||
sample 101:
|
||||
time = 37659197155
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 102:
|
||||
time = 37659213844
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 52:
|
||||
data = length 3, hash 7363
|
||||
sample 103:
|
||||
time = 37659230511
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 104:
|
||||
time = 37659247200
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 53:
|
||||
data = length 3, hash 7363
|
||||
sample 105:
|
||||
time = 37659263888
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 106:
|
||||
time = 37659280577
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 54:
|
||||
data = length 3, hash 7363
|
||||
sample 107:
|
||||
time = 37659297244
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 108:
|
||||
time = 37659313933
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 55:
|
||||
data = length 3, hash 7363
|
||||
sample 109:
|
||||
time = 37659330622
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 110:
|
||||
time = 37659347311
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 56:
|
||||
data = length 3, hash 7363
|
||||
sample 111:
|
||||
time = 37659363977
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 112:
|
||||
time = 37659380666
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 57:
|
||||
data = length 3, hash 7363
|
||||
sample 113:
|
||||
time = 37659397355
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 114:
|
||||
time = 37659414044
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 58:
|
||||
data = length 3, hash 7363
|
||||
sample 115:
|
||||
time = 37659430711
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 116:
|
||||
time = 37659447400
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 59:
|
||||
data = length 3, hash 7363
|
||||
sample 117:
|
||||
time = 37659464088
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 118:
|
||||
time = 37659480777
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 60:
|
||||
data = length 3, hash 7363
|
||||
sample 119:
|
||||
time = 37659497444
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 120:
|
||||
time = 37659514133
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 61:
|
||||
data = length 3, hash 7363
|
||||
sample 121:
|
||||
time = 37659530822
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 122:
|
||||
time = 37659547511
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 62:
|
||||
data = length 3, hash 7363
|
||||
sample 123:
|
||||
time = 37659564177
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 124:
|
||||
time = 37659580866
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF3C1
|
||||
sample 63:
|
||||
data = length 3, hash 7363
|
||||
sample 125:
|
||||
time = 37659597555
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 126:
|
||||
time = 37659614244
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6CD
|
||||
sample 64:
|
||||
data = length 3, hash 766F
|
||||
sample 127:
|
||||
time = 37659630911
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 128:
|
||||
time = 37659647600
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6DC
|
||||
sample 65:
|
||||
data = length 3, hash 767E
|
||||
sample 129:
|
||||
time = 37659664288
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 130:
|
||||
time = 37659680977
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF65B
|
||||
sample 66:
|
||||
data = length 15, hash 191B585A
|
||||
sample 131:
|
||||
time = 37659697644
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 132:
|
||||
time = 37659714333
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6CD
|
||||
sample 67:
|
||||
data = length 12, hash 15EC5FC5
|
||||
sample 133:
|
||||
time = 37659731022
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 134:
|
||||
time = 37659747711
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6FF
|
||||
sample 68:
|
||||
data = length 3, hash 76A1
|
||||
sample 135:
|
||||
time = 37659764377
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 136:
|
||||
time = 37659781066
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF6AC
|
||||
sample 69:
|
||||
data = length 30, hash E8012479
|
||||
sample 137:
|
||||
time = 37659797755
|
||||
flags = 1
|
||||
data = length 6, hash 36289D5E
|
||||
sample 138:
|
||||
time = 37659814444
|
||||
flags = 1
|
||||
data = length 2, hash FFFFF5FE
|
||||
sample 70:
|
||||
data = length 12, hash D32F29F3
|
||||
sample 139:
|
||||
time = 37659831111
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 140:
|
||||
time = 37659847800
|
||||
flags = 1
|
||||
data = length 2, hash FFFFFEF7
|
||||
sample 71:
|
||||
data = length 21, hash 6258623
|
||||
sample 141:
|
||||
time = 37659864488
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 142:
|
||||
time = 37659881177
|
||||
flags = 1
|
||||
data = length 2, hash 120C
|
||||
sample 72:
|
||||
data = length 12, hash FE69ABA2
|
||||
sample 143:
|
||||
time = 37659897844
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 144:
|
||||
time = 37659914533
|
||||
flags = 1
|
||||
data = length 2, hash 1124
|
||||
sample 73:
|
||||
data = length 12, hash 958D0815
|
||||
sample 145:
|
||||
time = 37659931222
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 146:
|
||||
time = 37659947911
|
||||
flags = 1
|
||||
data = length 2, hash 1A9
|
||||
sample 74:
|
||||
data = length 12, hash FF57BFD8
|
||||
sample 147:
|
||||
time = 37659964577
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
sample 148:
|
||||
time = 37659981266
|
||||
flags = 1
|
||||
data = length 2, hash 935
|
||||
data = length 12, hash 922122E7
|
||||
sample 149:
|
||||
time = 37659997955
|
||||
flags = 1
|
||||
data = length 3, hash 7724
|
||||
tracksEnded = true
|
||||
|
@ -33,14 +33,14 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};
|
||||
private static final int LARGE_TEST_DATA_LENGTH = 8192;
|
||||
|
||||
public void testInitialPosition() throws IOException {
|
||||
public void testInitialPosition() throws Exception {
|
||||
FakeDataSource testDataSource = buildDataSource();
|
||||
DefaultExtractorInput input =
|
||||
new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET);
|
||||
assertEquals(123, input.getPosition());
|
||||
}
|
||||
|
||||
public void testRead() throws IOException, InterruptedException {
|
||||
public void testRead() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
// We expect to perform three reads of three bytes, as setup in buildTestDataSource.
|
||||
@ -58,7 +58,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
||||
}
|
||||
|
||||
public void testReadPeeked() throws IOException, InterruptedException {
|
||||
public void testReadPeeked() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -71,7 +71,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertTrue(Arrays.equals(TEST_DATA, target));
|
||||
}
|
||||
|
||||
public void testReadMoreDataPeeked() throws IOException, InterruptedException {
|
||||
public void testReadMoreDataPeeked() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -84,7 +84,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertTrue(Arrays.equals(TEST_DATA, target));
|
||||
}
|
||||
|
||||
public void testReadFullyOnce() throws IOException, InterruptedException {
|
||||
public void testReadFullyOnce() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
input.readFully(target, 0, TEST_DATA.length);
|
||||
@ -103,7 +103,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadFullyTwice() throws IOException, InterruptedException {
|
||||
public void testReadFullyTwice() throws Exception {
|
||||
// Read TEST_DATA in two parts.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[5];
|
||||
@ -116,7 +116,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(5 + 4, input.getPosition());
|
||||
}
|
||||
|
||||
public void testReadFullyTooMuch() throws IOException, InterruptedException {
|
||||
public void testReadFullyTooMuch() throws Exception {
|
||||
// Read more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
try {
|
||||
@ -141,7 +141,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(0, input.getPosition());
|
||||
}
|
||||
|
||||
public void testReadFullyWithFailingDataSource() throws IOException, InterruptedException {
|
||||
public void testReadFullyWithFailingDataSource() throws Exception {
|
||||
FakeDataSource testDataSource = buildFailingDataSource();
|
||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||
try {
|
||||
@ -155,7 +155,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(0, input.getPosition());
|
||||
}
|
||||
|
||||
public void testReadFullyHalfPeeked() throws IOException, InterruptedException {
|
||||
public void testReadFullyHalfPeeked() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -168,7 +168,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(TEST_DATA.length, input.getPosition());
|
||||
}
|
||||
|
||||
public void testSkip() throws IOException, InterruptedException {
|
||||
public void testSkip() throws Exception {
|
||||
FakeDataSource testDataSource = buildDataSource();
|
||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||
// We expect to perform three skips of three bytes, as setup in buildTestDataSource.
|
||||
@ -180,7 +180,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
||||
}
|
||||
|
||||
public void testLargeSkip() throws IOException, InterruptedException {
|
||||
public void testLargeSkip() throws Exception {
|
||||
FakeDataSource testDataSource = buildLargeDataSource();
|
||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||
// Check that skipping the entire data source succeeds.
|
||||
@ -190,7 +190,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testSkipFullyOnce() throws IOException, InterruptedException {
|
||||
public void testSkipFullyOnce() throws Exception {
|
||||
// Skip TEST_DATA.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
input.skipFully(TEST_DATA.length);
|
||||
@ -207,7 +207,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testSkipFullyTwice() throws IOException, InterruptedException {
|
||||
public void testSkipFullyTwice() throws Exception {
|
||||
// Skip TEST_DATA in two parts.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
input.skipFully(5);
|
||||
@ -216,7 +216,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(5 + 4, input.getPosition());
|
||||
}
|
||||
|
||||
public void testSkipFullyTwicePeeked() throws IOException, InterruptedException {
|
||||
public void testSkipFullyTwicePeeked() throws Exception {
|
||||
// Skip TEST_DATA.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
|
||||
@ -230,7 +230,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(TEST_DATA.length, input.getPosition());
|
||||
}
|
||||
|
||||
public void testSkipFullyTooMuch() throws IOException, InterruptedException {
|
||||
public void testSkipFullyTooMuch() throws Exception {
|
||||
// Skip more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
try {
|
||||
@ -253,7 +253,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(0, input.getPosition());
|
||||
}
|
||||
|
||||
public void testSkipFullyWithFailingDataSource() throws IOException, InterruptedException {
|
||||
public void testSkipFullyWithFailingDataSource() throws Exception {
|
||||
FakeDataSource testDataSource = buildFailingDataSource();
|
||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||
try {
|
||||
@ -266,7 +266,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertEquals(0, input.getPosition());
|
||||
}
|
||||
|
||||
public void testSkipFullyLarge() throws IOException, InterruptedException {
|
||||
public void testSkipFullyLarge() throws Exception {
|
||||
// Tests skipping an amount of data that's larger than any internal scratch space.
|
||||
int largeSkipSize = 1024 * 1024;
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
@ -286,7 +286,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testPeekFully() throws IOException, InterruptedException {
|
||||
public void testPeekFully() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
input.peekFully(target, 0, TEST_DATA.length);
|
||||
@ -312,7 +312,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testResetPeekPosition() throws IOException, InterruptedException {
|
||||
public void testResetPeekPosition() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
input.peekFully(target, 0, TEST_DATA.length);
|
||||
@ -336,8 +336,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds()
|
||||
throws IOException, InterruptedException {
|
||||
public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -348,8 +347,24 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
assertFalse(input.peekFully(target, 0, 1, true));
|
||||
}
|
||||
|
||||
public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails()
|
||||
throws IOException, InterruptedException {
|
||||
public void testPeekFullyAtEndThenReadEndOfInput() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
// Peek up to the end of the input.
|
||||
assertTrue(input.peekFully(target, 0, TEST_DATA.length, false));
|
||||
|
||||
// Peek the end of the input.
|
||||
assertFalse(input.peekFully(target, 0, 1, true));
|
||||
|
||||
// Read up to the end of the input.
|
||||
assertTrue(input.readFully(target, 0, TEST_DATA.length, false));
|
||||
|
||||
// Read the end of the input.
|
||||
assertFalse(input.readFully(target, 0, 1, true));
|
||||
}
|
||||
|
||||
public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -365,8 +380,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails()
|
||||
throws IOException, InterruptedException {
|
||||
public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception {
|
||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||
byte[] target = new byte[TEST_DATA.length];
|
||||
|
||||
@ -382,7 +396,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static FakeDataSource buildDataSource() throws IOException {
|
||||
private static FakeDataSource buildDataSource() throws Exception {
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3));
|
||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6));
|
||||
@ -392,7 +406,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
return testDataSource;
|
||||
}
|
||||
|
||||
private static FakeDataSource buildFailingDataSource() throws IOException {
|
||||
private static FakeDataSource buildFailingDataSource() throws Exception {
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6));
|
||||
builder.appendReadError(new IOException());
|
||||
@ -402,7 +416,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
return testDataSource;
|
||||
}
|
||||
|
||||
private static FakeDataSource buildLargeDataSource() throws IOException {
|
||||
private static FakeDataSource buildLargeDataSource() throws Exception {
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
|
||||
FakeDataSource testDataSource = builder.build();
|
||||
@ -410,8 +424,9 @@ public class DefaultExtractorInputTest extends TestCase {
|
||||
return testDataSource;
|
||||
}
|
||||
|
||||
private static DefaultExtractorInput createDefaultExtractorInput() throws IOException {
|
||||
private static DefaultExtractorInput createDefaultExtractorInput() throws Exception {
|
||||
FakeDataSource testDataSource = buildDataSource();
|
||||
return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
|
||||
}, "mp3/bear.mp3", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testTrimmedMp3Sample() throws Exception {
|
||||
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
|
||||
@Override
|
||||
public Extractor create() {
|
||||
return new Mp3Extractor();
|
||||
}
|
||||
}, "mp3/play-trimmed.mp3", getInstrumentation());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,8 +16,16 @@
|
||||
package com.google.android.exoplayer2.extractor.ts;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
||||
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
||||
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
||||
}, "ts/sample.ts", fileData, getInstrumentation());
|
||||
}
|
||||
|
||||
public void testCustomPesReader() throws Exception {
|
||||
CustomEsReaderFactory factory = new CustomEsReaderFactory();
|
||||
TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory);
|
||||
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
|
||||
.setSimulateIOErrors(false)
|
||||
.setSimulateUnknownLength(false)
|
||||
.setSimulatePartialReads(false).build();
|
||||
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||
tsExtractor.init(output);
|
||||
tsExtractor.seek(input.getPosition());
|
||||
PositionHolder seekPositionHolder = new PositionHolder();
|
||||
int readResult = Extractor.RESULT_CONTINUE;
|
||||
while (readResult != Extractor.RESULT_END_OF_INPUT) {
|
||||
readResult = tsExtractor.read(input, seekPositionHolder);
|
||||
}
|
||||
CustomEsReader reader = factory.reader;
|
||||
assertEquals(2, reader.packetsRead);
|
||||
TrackOutput trackOutput = reader.getTrackOutput();
|
||||
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
|
||||
assertEquals(
|
||||
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
|
||||
((FakeTrackOutput) trackOutput).format);
|
||||
}
|
||||
|
||||
private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (((byte) i) == TS_SYNC_BYTE) {
|
||||
@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CustomEsReader extends ElementaryStreamReader {
|
||||
|
||||
public int packetsRead = 0;
|
||||
|
||||
public CustomEsReader(TrackOutput output, String language) {
|
||||
super(output);
|
||||
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
|
||||
language, null, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetFinished() {
|
||||
packetsRead++;
|
||||
}
|
||||
|
||||
public TrackOutput getTrackOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory {
|
||||
|
||||
private final ElementaryStreamReader.Factory defaultFactory;
|
||||
private CustomEsReader reader;
|
||||
|
||||
public CustomEsReaderFactory() {
|
||||
defaultFactory = new DefaultStreamReaderFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
|
||||
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
|
||||
if (streamType == 3) {
|
||||
// We need to manually avoid a duplicate custom reader creation.
|
||||
if (reader == null) {
|
||||
reader = new CustomEsReader(output.track(pid), esInfo.language);
|
||||
}
|
||||
return reader;
|
||||
} else {
|
||||
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,11 @@ package com.google.android.exoplayer2;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.MediaCodec;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -70,55 +73,79 @@ public final class C {
|
||||
*/
|
||||
public static final String UTF8_NAME = "UTF-8";
|
||||
|
||||
/**
|
||||
* Crypto modes for a codec.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
||||
public @interface CryptoMode {}
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_AES_CTR
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_AES_CBC
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
||||
|
||||
/**
|
||||
* Represents an audio encoding, or an invalid or unset value.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
||||
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS,
|
||||
ENCODING_DTS_HD})
|
||||
public @interface Encoding {}
|
||||
|
||||
/**
|
||||
* Represents a PCM audio encoding, or an invalid or unset value.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
||||
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT})
|
||||
public @interface PcmEncoding {}
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_INVALID
|
||||
*/
|
||||
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_PCM_8BIT
|
||||
*/
|
||||
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_PCM_16BIT
|
||||
*/
|
||||
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
||||
|
||||
/**
|
||||
* PCM encoding with 24 bits per sample.
|
||||
*/
|
||||
public static final int ENCODING_PCM_24BIT = 0x80000000;
|
||||
|
||||
/**
|
||||
* PCM encoding with 32 bits per sample.
|
||||
*/
|
||||
public static final int ENCODING_PCM_32BIT = 0x40000000;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AC3
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_E_AC3
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_DTS
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
||||
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_DTS_HD
|
||||
*/
|
||||
@ -132,48 +159,93 @@ public final class C {
|
||||
public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
|
||||
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
|
||||
/**
|
||||
* Flags which can apply to a buffer containing a media sample.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@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.
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
|
||||
|
||||
/**
|
||||
* Flag for empty buffers that signal that the end of the stream was reached.
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||
|
||||
/**
|
||||
* Indicates that a buffer is (at least partially) encrypted.
|
||||
*/
|
||||
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
|
||||
|
||||
/**
|
||||
* Indicates that a buffer should be decoded but not rendered.
|
||||
*/
|
||||
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000;
|
||||
|
||||
/**
|
||||
* Track selection flags.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@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.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_DEFAULT = 1;
|
||||
/**
|
||||
* Indicates that the track must be displayed. Only applies to text tracks.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_FORCED = 2;
|
||||
/**
|
||||
* Indicates that the player may choose to play the track in absence of an explicit user
|
||||
* preference.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_AUTOSELECT = 4;
|
||||
|
||||
/**
|
||||
* Represents a streaming or other media type.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
|
||||
public @interface ContentType {}
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for DASH manifests.
|
||||
*/
|
||||
public static final int TYPE_DASH = 0;
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests.
|
||||
*/
|
||||
public static final int TYPE_SS = 1;
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for HLS manifests.
|
||||
*/
|
||||
public static final int TYPE_HLS = 2;
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
|
||||
* Smooth Streaming manifests.
|
||||
*/
|
||||
public static final int TYPE_OTHER = 3;
|
||||
|
||||
/**
|
||||
* A return value for methods where the end of an input was encountered.
|
||||
*/
|
||||
public static final int RESULT_END_OF_INPUT = -1;
|
||||
|
||||
/**
|
||||
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
|
||||
*/
|
||||
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
|
||||
|
||||
/**
|
||||
* A return value for methods where nothing was read.
|
||||
*/
|
||||
public static final int RESULT_NOTHING_READ = -3;
|
||||
|
||||
/**
|
||||
* A return value for methods where a buffer was read.
|
||||
*/
|
||||
public static final int RESULT_BUFFER_READ = -4;
|
||||
|
||||
/**
|
||||
* A return value for methods where a format was read.
|
||||
*/
|
||||
@ -183,32 +255,26 @@ public final class C {
|
||||
* A data type constant for data of unknown or unspecified type.
|
||||
*/
|
||||
public static final int DATA_TYPE_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* A data type constant for media, typically containing media samples.
|
||||
*/
|
||||
public static final int DATA_TYPE_MEDIA = 1;
|
||||
|
||||
/**
|
||||
* A data type constant for media, typically containing only initialization data.
|
||||
*/
|
||||
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
|
||||
|
||||
/**
|
||||
* A data type constant for drm or encryption data.
|
||||
*/
|
||||
public static final int DATA_TYPE_DRM = 3;
|
||||
|
||||
/**
|
||||
* A data type constant for a manifest file.
|
||||
*/
|
||||
public static final int DATA_TYPE_MANIFEST = 4;
|
||||
|
||||
/**
|
||||
* A data type constant for time synchronization data.
|
||||
*/
|
||||
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
|
||||
|
||||
/**
|
||||
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
|
||||
* equal to this value.
|
||||
@ -219,32 +285,26 @@ public final class C {
|
||||
* 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.
|
||||
*/
|
||||
public static final int TRACK_TYPE_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* A type constant for audio tracks.
|
||||
*/
|
||||
public static final int TRACK_TYPE_AUDIO = 1;
|
||||
|
||||
/**
|
||||
* A type constant for video tracks.
|
||||
*/
|
||||
public static final int TRACK_TYPE_VIDEO = 2;
|
||||
|
||||
/**
|
||||
* A type constant for text tracks.
|
||||
*/
|
||||
public static final int TRACK_TYPE_TEXT = 3;
|
||||
|
||||
/**
|
||||
* A type constant for metadata tracks.
|
||||
*/
|
||||
public static final int TRACK_TYPE_METADATA = 4;
|
||||
|
||||
/**
|
||||
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
|
||||
* equal to this value.
|
||||
@ -255,27 +315,22 @@ public final class C {
|
||||
* A selection reason constant for selections whose reasons are unknown or unspecified.
|
||||
*/
|
||||
public static final int SELECTION_REASON_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* A selection reason constant for an initial track selection.
|
||||
*/
|
||||
public static final int SELECTION_REASON_INITIAL = 1;
|
||||
|
||||
/**
|
||||
* A selection reason constant for an manual (i.e. user initiated) track selection.
|
||||
*/
|
||||
public static final int SELECTION_REASON_MANUAL = 2;
|
||||
|
||||
/**
|
||||
* A selection reason constant for an adaptive track selection.
|
||||
*/
|
||||
public static final int SELECTION_REASON_ADAPTIVE = 3;
|
||||
|
||||
/**
|
||||
* A selection reason constant for a trick play track selection.
|
||||
*/
|
||||
public static final int SELECTION_REASON_TRICK_PLAY = 4;
|
||||
|
||||
/**
|
||||
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
|
||||
* or equal to this value.
|
||||
@ -363,16 +418,20 @@ public final class C {
|
||||
*/
|
||||
public static final int MSG_CUSTOM_BASE = 10000;
|
||||
|
||||
/**
|
||||
* The stereo mode for 360/3D/VR videos.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
|
||||
public @interface StereoMode {}
|
||||
/**
|
||||
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
|
||||
*/
|
||||
public static final int STEREO_MODE_MONO = 0;
|
||||
|
||||
/**
|
||||
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
|
||||
*/
|
||||
public static final int STEREO_MODE_TOP_BOTTOM = 1;
|
||||
|
||||
/**
|
||||
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
|
||||
@Override
|
||||
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||
TrackSelectionArray trackSelections) {
|
||||
TrackSelections<?> trackSelections) {
|
||||
targetBufferSize = 0;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (trackSelections.get(i) != null) {
|
||||
|
@ -15,15 +15,24 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Thrown when a non-recoverable playback failure occurs.
|
||||
*/
|
||||
public final class ExoPlaybackException extends Exception {
|
||||
|
||||
/**
|
||||
* The type of source that produced the error.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})
|
||||
public @interface Type {}
|
||||
/**
|
||||
* The error occurred loading data from a {@link MediaSource}.
|
||||
* <p>
|
||||
@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception {
|
||||
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
|
||||
* {@link #TYPE_UNEXPECTED}.
|
||||
*/
|
||||
@Type
|
||||
public final int type;
|
||||
|
||||
/**
|
||||
@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception {
|
||||
return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
private ExoPlaybackException(int type, String message, Throwable cause, int rendererIndex) {
|
||||
private ExoPlaybackException(@Type int type, String message, Throwable cause,
|
||||
int rendererIndex) {
|
||||
super(message, cause);
|
||||
this.type = type;
|
||||
this.rendererIndex = rendererIndex;
|
||||
|
@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
if (timeline == null || pendingSeekAcks > 0) {
|
||||
return maskingWindowPositionMs;
|
||||
} else {
|
||||
int periodIndex = playbackInfo.periodIndex;
|
||||
timeline.getPeriod(periodIndex, period);
|
||||
int windowIndex = period.windowIndex;
|
||||
timeline.getWindow(windowIndex, window);
|
||||
if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
|
||||
&& window.getPositionInFirstPeriodUs() == 0
|
||||
&& window.getDurationUs() == period.getDurationUs()) {
|
||||
return C.usToMs(playbackInfo.bufferedPositionUs);
|
||||
}
|
||||
return getCurrentPosition();
|
||||
timeline.getPeriod(playbackInfo.periodIndex, period);
|
||||
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.MediaClock;
|
||||
@ -40,8 +40,8 @@ import java.io.IOException;
|
||||
/**
|
||||
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
||||
*/
|
||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback,
|
||||
TrackSelector.InvalidationListener, MediaSource.Listener {
|
||||
/* package */ final class ExoPlayerImplInternal<T> implements Handler.Callback,
|
||||
MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener {
|
||||
|
||||
/**
|
||||
* Playback position information which is read on the application's thread by
|
||||
@ -100,7 +100,7 @@ import java.io.IOException;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
private final TrackSelector trackSelector;
|
||||
private final TrackSelector<T> trackSelector;
|
||||
private final LoadControl loadControl;
|
||||
private final StandaloneMediaClock standaloneMediaClock;
|
||||
private final Handler handler;
|
||||
@ -128,13 +128,13 @@ import java.io.IOException;
|
||||
private boolean isTimelineReady;
|
||||
private boolean isTimelineEnded;
|
||||
private int bufferAheadPeriodCount;
|
||||
private MediaPeriodHolder playingPeriodHolder;
|
||||
private MediaPeriodHolder readingPeriodHolder;
|
||||
private MediaPeriodHolder loadingPeriodHolder;
|
||||
private MediaPeriodHolder<T> playingPeriodHolder;
|
||||
private MediaPeriodHolder<T> readingPeriodHolder;
|
||||
private MediaPeriodHolder<T> loadingPeriodHolder;
|
||||
|
||||
private Timeline timeline;
|
||||
|
||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector<T> trackSelector,
|
||||
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
||||
PlaybackInfo playbackInfo) {
|
||||
this.renderers = renderers;
|
||||
@ -458,12 +458,11 @@ import java.io.IOException;
|
||||
startRenderers();
|
||||
}
|
||||
}
|
||||
} else if (state == ExoPlayer.STATE_READY) {
|
||||
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady) {
|
||||
rebuffering = playWhenReady;
|
||||
setState(ExoPlayer.STATE_BUFFERING);
|
||||
stopRenderers();
|
||||
}
|
||||
} else if (state == ExoPlayer.STATE_READY
|
||||
&& (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady)) {
|
||||
rebuffering = playWhenReady;
|
||||
setState(ExoPlayer.STATE_BUFFERING);
|
||||
stopRenderers();
|
||||
}
|
||||
|
||||
if (state == ExoPlayer.STATE_BUFFERING) {
|
||||
@ -530,17 +529,17 @@ import java.io.IOException;
|
||||
rebuffering = false;
|
||||
setState(ExoPlayer.STATE_BUFFERING);
|
||||
|
||||
if (periodPositionUs == C.TIME_UNSET
|
||||
|| (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index
|
||||
|| (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) {
|
||||
if (periodPositionUs == C.TIME_UNSET || (readingPeriodHolder != playingPeriodHolder
|
||||
&& (periodIndex == playingPeriodHolder.index
|
||||
|| periodIndex == readingPeriodHolder.index))) {
|
||||
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
||||
// ahead to the next period and the seek is to either the playing or reading period.
|
||||
periodIndex = C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
// Clear the timeline, but keep the requested period if it is already prepared.
|
||||
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
||||
MediaPeriodHolder newPlayingPeriodHolder = null;
|
||||
MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
|
||||
MediaPeriodHolder<T> newPlayingPeriodHolder = null;
|
||||
while (periodHolder != null) {
|
||||
if (periodHolder.index == periodIndex && periodHolder.prepared) {
|
||||
newPlayingPeriodHolder = periodHolder;
|
||||
@ -672,7 +671,7 @@ import java.io.IOException;
|
||||
return;
|
||||
}
|
||||
// Reselect tracks on each period in turn, until the selection changes.
|
||||
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
||||
MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
|
||||
boolean selectionsChangedForReadPeriod = true;
|
||||
while (true) {
|
||||
if (periodHolder == null || !periodHolder.prepared) {
|
||||
@ -691,16 +690,14 @@ import java.io.IOException;
|
||||
}
|
||||
|
||||
if (selectionsChangedForReadPeriod) {
|
||||
// Release everything after the playing period because a renderer may have read data from a
|
||||
// track whose selection has now changed.
|
||||
// Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
|
||||
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
|
||||
releasePeriodHoldersFrom(playingPeriodHolder.next);
|
||||
playingPeriodHolder.next = null;
|
||||
readingPeriodHolder = playingPeriodHolder;
|
||||
loadingPeriodHolder = playingPeriodHolder;
|
||||
bufferAheadPeriodCount = 0;
|
||||
|
||||
// Update streams for the new selection, recreating all streams if reading ahead.
|
||||
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
|
||||
boolean[] streamResetFlags = new boolean[renderers.length];
|
||||
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
|
||||
playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
|
||||
@ -739,7 +736,7 @@ import java.io.IOException;
|
||||
}
|
||||
}
|
||||
}
|
||||
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData);
|
||||
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelections);
|
||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||
} else {
|
||||
// Release and re-prepare/buffer periods after the one whose selection changed.
|
||||
@ -811,11 +808,11 @@ import java.io.IOException;
|
||||
playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window),
|
||||
index);
|
||||
|
||||
MediaPeriodHolder previousPeriod = playingPeriodHolder;
|
||||
MediaPeriodHolder<T> previousPeriodHolder = playingPeriodHolder;
|
||||
boolean seenReadingPeriod = false;
|
||||
bufferAheadPeriodCount = 0;
|
||||
while (previousPeriod.next != null) {
|
||||
MediaPeriodHolder periodHolder = previousPeriod.next;
|
||||
while (previousPeriodHolder.next != null) {
|
||||
MediaPeriodHolder<T> periodHolder = previousPeriodHolder.next;
|
||||
index++;
|
||||
timeline.getPeriod(index, period, true);
|
||||
if (!periodHolder.uid.equals(period.uid)) {
|
||||
@ -836,7 +833,7 @@ import java.io.IOException;
|
||||
}
|
||||
|
||||
// Update the loading period to be the latest period that is still valid.
|
||||
loadingPeriodHolder = previousPeriod;
|
||||
loadingPeriodHolder = previousPeriodHolder;
|
||||
loadingPeriodHolder.next = null;
|
||||
|
||||
// Release the rest of the timeline.
|
||||
@ -850,7 +847,7 @@ import java.io.IOException;
|
||||
if (periodHolder == readingPeriodHolder) {
|
||||
seenReadingPeriod = true;
|
||||
}
|
||||
previousPeriod = periodHolder;
|
||||
previousPeriodHolder = periodHolder;
|
||||
}
|
||||
} else if (loadingPeriodHolder != null) {
|
||||
Object uid = loadingPeriodHolder.uid;
|
||||
@ -953,10 +950,12 @@ import java.io.IOException;
|
||||
periodStartPositionUs = defaultPosition.second;
|
||||
}
|
||||
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
|
||||
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this,
|
||||
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex,
|
||||
loadControl.getAllocator(), periodStartPositionUs);
|
||||
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
|
||||
trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs);
|
||||
newMediaPeriod.prepare(this);
|
||||
MediaPeriodHolder<T> newPeriodHolder = new MediaPeriodHolder<>(renderers,
|
||||
rendererCapabilities, trackSelector, mediaSource, newMediaPeriod, newPeriodUid,
|
||||
periodStartPositionUs);
|
||||
timeline.getWindow(windowIndex, window);
|
||||
newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
|
||||
if (loadingPeriodHolder != null) {
|
||||
@ -995,19 +994,24 @@ import java.io.IOException;
|
||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||
}
|
||||
updateTimelineState();
|
||||
if (readingPeriodHolder == null) {
|
||||
|
||||
if (readingPeriodHolder.isLast) {
|
||||
// The renderers have their final SampleStreams.
|
||||
for (Renderer renderer : enabledRenderers) {
|
||||
renderer.setCurrentStreamIsFinal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (Renderer renderer : enabledRenderers) {
|
||||
if (!renderer.hasReadStreamToEnd()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
|
||||
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||
TrackSelections<T> oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||
readingPeriodHolder = readingPeriodHolder.next;
|
||||
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections;
|
||||
TrackSelections<T> newTrackSelections = readingPeriodHolder.trackSelections;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
TrackSelection oldSelection = oldTrackSelections.get(i);
|
||||
@ -1029,11 +1033,6 @@ import java.io.IOException;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (readingPeriodHolder.isLast) {
|
||||
readingPeriodHolder = null;
|
||||
for (Renderer renderer : enabledRenderers) {
|
||||
renderer.setCurrentStreamIsFinal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1071,7 +1070,7 @@ import java.io.IOException;
|
||||
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
||||
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
||||
long loadingPeriodPositionUs = rendererPositionUs
|
||||
- loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs;
|
||||
- loadingPeriodHolder.rendererPositionOffsetUs;
|
||||
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
||||
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
|
||||
setIsLoading(continueLoading);
|
||||
@ -1086,14 +1085,15 @@ import java.io.IOException;
|
||||
}
|
||||
}
|
||||
|
||||
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
|
||||
private void releasePeriodHoldersFrom(MediaPeriodHolder<T> periodHolder) {
|
||||
while (periodHolder != null) {
|
||||
periodHolder.release();
|
||||
periodHolder = periodHolder.next;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
|
||||
private void setPlayingPeriodHolder(MediaPeriodHolder<T> periodHolder)
|
||||
throws ExoPlaybackException {
|
||||
int enabledRendererCount = 0;
|
||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
@ -1116,7 +1116,7 @@ import java.io.IOException;
|
||||
}
|
||||
}
|
||||
|
||||
trackSelector.onSelectionActivated(periodHolder.trackSelectionData);
|
||||
trackSelector.onSelectionActivated(periodHolder.trackSelections);
|
||||
playingPeriodHolder = periodHolder;
|
||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||
}
|
||||
@ -1173,7 +1173,7 @@ import java.io.IOException;
|
||||
/**
|
||||
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
|
||||
*/
|
||||
private static final class MediaPeriodHolder {
|
||||
private static final class MediaPeriodHolder<T> {
|
||||
|
||||
public final MediaPeriod mediaPeriod;
|
||||
public final Object uid;
|
||||
@ -1187,21 +1187,20 @@ import java.io.IOException;
|
||||
public boolean prepared;
|
||||
public boolean hasEnabledTracks;
|
||||
public long rendererPositionOffsetUs;
|
||||
public MediaPeriodHolder next;
|
||||
public MediaPeriodHolder<T> next;
|
||||
public boolean needsContinueLoading;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
private final TrackSelector trackSelector;
|
||||
private final TrackSelector<T> trackSelector;
|
||||
private final MediaSource mediaSource;
|
||||
|
||||
private Object trackSelectionData;
|
||||
private TrackSelectionArray trackSelections;
|
||||
private TrackSelectionArray periodTrackSelections;
|
||||
private TrackSelections<T> trackSelections;
|
||||
private TrackSelections<T> periodTrackSelections;
|
||||
|
||||
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid,
|
||||
long positionUs) {
|
||||
TrackSelector<T> trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod,
|
||||
Object uid, long positionUs) {
|
||||
this.renderers = renderers;
|
||||
this.rendererCapabilities = rendererCapabilities;
|
||||
this.trackSelector = trackSelector;
|
||||
@ -1213,7 +1212,7 @@ import java.io.IOException;
|
||||
startPositionUs = positionUs;
|
||||
}
|
||||
|
||||
public void setNext(MediaPeriodHolder next) {
|
||||
public void setNext(MediaPeriodHolder<T> next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@ -1235,14 +1234,12 @@ import java.io.IOException;
|
||||
}
|
||||
|
||||
public boolean selectTracks() throws ExoPlaybackException {
|
||||
Pair<TrackSelectionArray, Object> result =
|
||||
trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups());
|
||||
TrackSelectionArray newTrackSelections = result.first;
|
||||
TrackSelections<T> newTrackSelections = trackSelector.selectTracks(rendererCapabilities,
|
||||
mediaPeriod.getTrackGroups());
|
||||
if (newTrackSelections.equals(periodTrackSelections)) {
|
||||
return false;
|
||||
}
|
||||
trackSelections = newTrackSelections;
|
||||
trackSelectionData = result.second;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
|
||||
/**
|
||||
* The version of the library, expressed as a string.
|
||||
*/
|
||||
String VERSION = "2.0.0";
|
||||
String VERSION = "2.0.1";
|
||||
|
||||
/**
|
||||
* The version of the library, expressed as an integer.
|
||||
@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
|
||||
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
||||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
int VERSION_INT = 2000000;
|
||||
int VERSION_INT = 2000001;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||
|
@ -39,20 +39,6 @@ public final class Format implements Parcelable {
|
||||
*/
|
||||
public static final int NO_VALUE = -1;
|
||||
|
||||
/**
|
||||
* Indicates that the track should be selected if user preferences do not state otherwise.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_DEFAULT = 1;
|
||||
/**
|
||||
* Indicates that the track must be displayed. Only applies to text tracks.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_FORCED = 2;
|
||||
/**
|
||||
* Indicates that the player may choose to play the track in absence of an explicit user
|
||||
* preference.
|
||||
*/
|
||||
public static final int SELECTION_FLAG_AUTOSELECT = 4;
|
||||
|
||||
/**
|
||||
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
|
||||
* the timestamps of their parent samples.
|
||||
@ -131,6 +117,7 @@ public final class Format implements Parcelable {
|
||||
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
|
||||
* C#STEREO_MODE_LEFT_RIGHT}.
|
||||
*/
|
||||
@C.StereoMode
|
||||
public final int stereoMode;
|
||||
/**
|
||||
* The projection data for 360/VR video, or null if not applicable.
|
||||
@ -153,6 +140,7 @@ public final class Format implements Parcelable {
|
||||
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
|
||||
* other media types.
|
||||
*/
|
||||
@C.PcmEncoding
|
||||
public final int pcmEncoding;
|
||||
/**
|
||||
* The number of samples to trim from the start of the decoded audio stream.
|
||||
@ -177,6 +165,7 @@ public final class Format implements Parcelable {
|
||||
/**
|
||||
* Track selection flags.
|
||||
*/
|
||||
@C.SelectionFlags
|
||||
public final int selectionFlags;
|
||||
|
||||
/**
|
||||
@ -218,7 +207,7 @@ public final class Format implements Parcelable {
|
||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int maxInputSize, int width, int height, float frameRate,
|
||||
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
|
||||
byte[] projectionData, int stereoMode, DrmInitData drmInitData) {
|
||||
byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) {
|
||||
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
|
||||
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData,
|
||||
@ -229,7 +218,7 @@ public final class Format implements Parcelable {
|
||||
|
||||
public static Format createAudioContainerFormat(String id, String containerMimeType,
|
||||
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
|
||||
List<byte[]> initializationData, int selectionFlags, String language) {
|
||||
List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData,
|
||||
@ -238,25 +227,26 @@ public final class Format implements Parcelable {
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
|
||||
String language) {
|
||||
List<byte[]> initializationData, DrmInitData drmInitData,
|
||||
@C.SelectionFlags int selectionFlags, String language) {
|
||||
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
||||
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
|
||||
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
|
||||
String language) {
|
||||
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||
@C.PcmEncoding int pcmEncoding, List<byte[]> initializationData, DrmInitData drmInitData,
|
||||
@C.SelectionFlags int selectionFlags, String language) {
|
||||
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
||||
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
|
||||
selectionFlags, language);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
|
||||
int encoderDelay, int encoderPadding, List<byte[]> initializationData,
|
||||
DrmInitData drmInitData, int selectionFlags, String language) {
|
||||
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||
@C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding,
|
||||
List<byte[]> initializationData, DrmInitData drmInitData,
|
||||
@C.SelectionFlags int selectionFlags, String language) {
|
||||
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
||||
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
|
||||
@ -266,20 +256,21 @@ public final class Format implements Parcelable {
|
||||
// Text.
|
||||
|
||||
public static Format createTextContainerFormat(String id, String containerMimeType,
|
||||
String sampleMimeType, String codecs, int bitrate, int selectionFlags, String language) {
|
||||
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
|
||||
String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
|
||||
}
|
||||
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int selectionFlags, String language, DrmInitData drmInitData) {
|
||||
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
|
||||
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
||||
drmInitData, OFFSET_SAMPLE_RELATIVE);
|
||||
}
|
||||
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||
int bitrate, int selectionFlags, String language, DrmInitData drmInitData,
|
||||
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
|
||||
long subsampleOffsetUs) {
|
||||
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
@ -313,10 +304,10 @@ public final class Format implements Parcelable {
|
||||
|
||||
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
|
||||
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
||||
float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount,
|
||||
int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags,
|
||||
String language, long subsampleOffsetUs, List<byte[]> initializationData,
|
||||
DrmInitData drmInitData) {
|
||||
float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
|
||||
int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
|
||||
int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
|
||||
long subsampleOffsetUs, List<byte[]> initializationData, DrmInitData drmInitData) {
|
||||
this.id = id;
|
||||
this.containerMimeType = containerMimeType;
|
||||
this.sampleMimeType = sampleMimeType;
|
||||
@ -343,6 +334,7 @@ public final class Format implements Parcelable {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType")
|
||||
/* package */ Format(Parcel in) {
|
||||
id = in.readString();
|
||||
containerMimeType = in.readString();
|
||||
@ -388,8 +380,8 @@ public final class Format implements Parcelable {
|
||||
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
|
||||
}
|
||||
|
||||
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
|
||||
int selectionFlags, String language) {
|
||||
public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
|
||||
@C.SelectionFlags int selectionFlags, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
|
||||
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
|
||||
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
|
||||
@ -402,7 +394,7 @@ public final class Format implements Parcelable {
|
||||
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
|
||||
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
|
||||
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
|
||||
int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
|
||||
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
|
||||
String language = this.language == null ? manifestFormat.language : this.language;
|
||||
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|
||||
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
|
||||
|
@ -17,8 +17,7 @@ package com.google.android.exoplayer2;
|
||||
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
/**
|
||||
@ -31,10 +30,10 @@ public interface LoadControl {
|
||||
*
|
||||
* @param renderers The renderers.
|
||||
* @param trackGroups The {@link TrackGroup}s from which the selection was made.
|
||||
* @param trackSelections The {@link TrackSelection}s that were made.
|
||||
* @param trackSelections The track selections that were made.
|
||||
*/
|
||||
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||
TrackSelectionArray trackSelections);
|
||||
TrackSelections<?> trackSelections);
|
||||
|
||||
/**
|
||||
* Called by the player when all tracks are disabled.
|
||||
|
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack;
|
||||
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||
@ -40,6 +41,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.TextRenderer;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
@ -80,18 +82,14 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
/**
|
||||
* Called when a frame is rendered for the first time since setting the surface, and when a
|
||||
* frame is rendered for the first time since the renderer was reset.
|
||||
*
|
||||
* @param surface The {@link Surface} to which a first frame has been rendered.
|
||||
* frame is rendered for the first time since a video track was selected.
|
||||
*/
|
||||
void onRenderedFirstFrame(Surface surface);
|
||||
void onRenderedFirstFrame();
|
||||
|
||||
/**
|
||||
* Called when the renderer is disabled.
|
||||
*
|
||||
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
||||
* Called when a video track is no longer selected.
|
||||
*/
|
||||
void onVideoDisabled(DecoderCounters counters);
|
||||
void onVideoTracksDisabled();
|
||||
|
||||
}
|
||||
|
||||
@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
private final int videoRendererCount;
|
||||
private final int audioRendererCount;
|
||||
|
||||
private boolean videoTracksEnabled;
|
||||
private Format videoFormat;
|
||||
private Format audioFormat;
|
||||
|
||||
private Surface surface;
|
||||
private SurfaceHolder surfaceHolder;
|
||||
private TextureView textureView;
|
||||
private TextRenderer.Output textOutput;
|
||||
@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
private float volume;
|
||||
private PlaybackParamsHolder playbackParamsHolder;
|
||||
|
||||
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
|
||||
LoadControl loadControl, DrmSessionManager drmSessionManager,
|
||||
/* package */ SimpleExoPlayer(Context context, TrackSelector<?> trackSelector,
|
||||
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
|
||||
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
|
||||
mainHandler = new Handler();
|
||||
componentListener = new ComponentListener();
|
||||
trackSelector.addListener(componentListener);
|
||||
|
||||
// Build the renderers.
|
||||
ArrayList<Renderer> renderersList = new ArrayList<>();
|
||||
@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void buildRenderers(Context context, DrmSessionManager drmSessionManager,
|
||||
ArrayList<Renderer> renderersList, long allowedVideoJoiningTimeMs) {
|
||||
private void buildRenderers(Context context,
|
||||
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList,
|
||||
long allowedVideoJoiningTimeMs) {
|
||||
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
|
||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
||||
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener,
|
||||
@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
}
|
||||
|
||||
private void setVideoSurfaceInternal(Surface surface) {
|
||||
this.surface = surface;
|
||||
ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount];
|
||||
int count = 0;
|
||||
for (Renderer renderer : renderers) {
|
||||
@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
private final class ComponentListener implements VideoRendererEventListener,
|
||||
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>,
|
||||
SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
|
||||
SurfaceHolder.Callback, TextureView.SurfaceTextureListener,
|
||||
TrackSelector.EventListener<Object> {
|
||||
|
||||
// VideoRendererEventListener implementation
|
||||
|
||||
@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame(Surface surface) {
|
||||
if (videoListener != null) {
|
||||
videoListener.onRenderedFirstFrame(surface);
|
||||
if (videoListener != null && SimpleExoPlayer.this.surface == surface) {
|
||||
videoListener.onRenderedFirstFrame();
|
||||
}
|
||||
if (videoDebugListener != null) {
|
||||
videoDebugListener.onRenderedFirstFrame(surface);
|
||||
@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
public void onVideoDisabled(DecoderCounters counters) {
|
||||
if (videoListener != null) {
|
||||
videoListener.onVideoDisabled(counters);
|
||||
}
|
||||
if (videoDebugListener != null) {
|
||||
videoDebugListener.onVideoDisabled(counters);
|
||||
}
|
||||
@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// TrackSelector.EventListener implementation
|
||||
|
||||
@Override
|
||||
public void onTrackSelectionsChanged(TrackSelections<?> trackSelections) {
|
||||
boolean videoTracksEnabled = false;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelections.get(i) != null) {
|
||||
videoTracksEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (videoListener != null && SimpleExoPlayer.this.videoTracksEnabled && !videoTracksEnabled) {
|
||||
videoListener.onVideoTracksDisabled();
|
||||
}
|
||||
SimpleExoPlayer.this.videoTracksEnabled = videoTracksEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
|
@ -208,7 +208,9 @@ public final class AudioTrack {
|
||||
private android.media.AudioTrack audioTrack;
|
||||
private int sampleRate;
|
||||
private int channelConfig;
|
||||
@C.Encoding
|
||||
private int sourceEncoding;
|
||||
@C.Encoding
|
||||
private int targetEncoding;
|
||||
private boolean passthrough;
|
||||
private int pcmFrameSize;
|
||||
@ -348,8 +350,8 @@ public final class AudioTrack {
|
||||
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
|
||||
* suitable buffer size automatically.
|
||||
*/
|
||||
public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding,
|
||||
int specifiedBufferSize) {
|
||||
public void configure(String mimeType, int channelCount, int sampleRate,
|
||||
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) {
|
||||
int channelConfig;
|
||||
switch (channelCount) {
|
||||
case 1:
|
||||
@ -381,7 +383,7 @@ public final class AudioTrack {
|
||||
}
|
||||
|
||||
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
||||
int sourceEncoding;
|
||||
@C.Encoding int sourceEncoding;
|
||||
if (passthrough) {
|
||||
sourceEncoding = getEncodingForMimeType(mimeType);
|
||||
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|
||||
@ -470,7 +472,7 @@ public final class AudioTrack {
|
||||
if (keepSessionIdAudioTrack == null) {
|
||||
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
|
||||
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
int encoding = C.ENCODING_PCM_16BIT;
|
||||
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
|
||||
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
|
||||
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
|
||||
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
|
||||
@ -962,7 +964,7 @@ public final class AudioTrack {
|
||||
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
|
||||
* capacity was insufficient for the output.
|
||||
*/
|
||||
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int sourceEncoding,
|
||||
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding,
|
||||
ByteBuffer out) {
|
||||
int offset = buffer.position();
|
||||
int limit = buffer.limit();
|
||||
@ -1023,6 +1025,7 @@ public final class AudioTrack {
|
||||
return resampledBuffer;
|
||||
}
|
||||
|
||||
@C.Encoding
|
||||
private static int getEncodingForMimeType(String mimeType) {
|
||||
switch (mimeType) {
|
||||
case MimeTypes.AUDIO_AC3:
|
||||
@ -1038,7 +1041,7 @@ public final class AudioTrack {
|
||||
}
|
||||
}
|
||||
|
||||
private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) {
|
||||
private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) {
|
||||
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
|
||||
return DtsUtil.parseDtsAudioSampleCount(buffer);
|
||||
} else if (encoding == C.ENCODING_AC3) {
|
||||
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
|
||||
*/
|
||||
public abstract class Buffer {
|
||||
|
||||
@C.BufferFlags
|
||||
private int flags;
|
||||
|
||||
/**
|
||||
@ -58,7 +59,7 @@ public abstract class Buffer {
|
||||
* @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
|
||||
* constants.
|
||||
*/
|
||||
public final void setFlags(int flags) {
|
||||
public final void setFlags(@C.BufferFlags int flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@ -68,7 +69,7 @@ public abstract class Buffer {
|
||||
* @param flag The flag to add to this buffer's flags, which should be one of the
|
||||
* {@code C.BUFFER_FLAG_*} constants.
|
||||
*/
|
||||
public final void addFlag(int flag) {
|
||||
public final void addFlag(@C.BufferFlags int flag) {
|
||||
flags |= flag;
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ public abstract class Buffer {
|
||||
*
|
||||
* @param flag The flag to remove.
|
||||
*/
|
||||
public final void clearFlag(int flag) {
|
||||
public final void clearFlag(@C.BufferFlags int flag) {
|
||||
flags &= ~flag;
|
||||
}
|
||||
|
||||
@ -87,7 +88,7 @@ public abstract class Buffer {
|
||||
* @param flag The flag to check.
|
||||
* @return Whether the flag is set.
|
||||
*/
|
||||
protected final boolean getFlag(int flag) {
|
||||
protected final boolean getFlag(@C.BufferFlags int flag) {
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.decoder;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
@ -34,6 +35,7 @@ public final class CryptoInfo {
|
||||
/**
|
||||
* @see android.media.MediaCodec.CryptoInfo#mode
|
||||
*/
|
||||
@C.CryptoMode
|
||||
public int mode;
|
||||
/**
|
||||
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
|
||||
@ -58,7 +60,7 @@ public final class CryptoInfo {
|
||||
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
|
||||
*/
|
||||
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
|
||||
byte[] key, byte[] iv, int mode) {
|
||||
byte[] key, byte[] iv, @C.CryptoMode int mode) {
|
||||
this.numSubSamples = numSubSamples;
|
||||
this.numBytesOfClearData = numBytesOfClearData;
|
||||
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
|
||||
|
@ -15,7 +15,10 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.decoder;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
@ -23,16 +26,21 @@ import java.nio.ByteBuffer;
|
||||
*/
|
||||
public class DecoderInputBuffer extends Buffer {
|
||||
|
||||
/**
|
||||
* The buffer replacement mode, which may disable replacement.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL,
|
||||
BUFFER_REPLACEMENT_MODE_DIRECT})
|
||||
public @interface BufferReplacementMode {}
|
||||
/**
|
||||
* Disallows buffer replacement.
|
||||
*/
|
||||
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
|
||||
*/
|
||||
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
|
||||
|
||||
/**
|
||||
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
|
||||
*/
|
||||
@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer {
|
||||
*/
|
||||
public long timeUs;
|
||||
|
||||
@BufferReplacementMode
|
||||
private final int bufferReplacementMode;
|
||||
|
||||
/**
|
||||
@ -60,7 +69,7 @@ public class DecoderInputBuffer extends Buffer {
|
||||
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
|
||||
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
|
||||
*/
|
||||
public DecoderInputBuffer(int bufferReplacementMode) {
|
||||
public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) {
|
||||
this.cryptoInfo = new CryptoInfo();
|
||||
this.bufferReplacementMode = bufferReplacementMode;
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
package com.google.android.exoplayer2.drm;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A DRM session.
|
||||
@ -23,6 +26,12 @@ import android.annotation.TargetApi;
|
||||
@TargetApi(16)
|
||||
public interface DrmSession<T extends ExoMediaCrypto> {
|
||||
|
||||
/**
|
||||
* The state of the DRM session.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
|
||||
@interface State {}
|
||||
/**
|
||||
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
|
||||
*/
|
||||
@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
|
||||
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
|
||||
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
|
||||
*/
|
||||
@State
|
||||
int getState();
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
|
||||
/**
|
||||
* Releases a {@link DrmSession}.
|
||||
*/
|
||||
void releaseSession(DrmSession drmSession);
|
||||
void releaseSession(DrmSession<T> drmSession);
|
||||
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import java.util.UUID;
|
||||
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
|
||||
*/
|
||||
@TargetApi(18)
|
||||
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager,
|
||||
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
|
||||
DrmSession<T> {
|
||||
|
||||
/**
|
||||
@ -87,6 +87,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
||||
|
||||
private int openCount;
|
||||
private boolean provisioningInProgress;
|
||||
@DrmSession.State
|
||||
private int state;
|
||||
private T mediaCrypto;
|
||||
private Exception lastException;
|
||||
@ -267,7 +268,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSession(DrmSession session) {
|
||||
public void releaseSession(DrmSession<T> session) {
|
||||
if (--openCount != 0) {
|
||||
return;
|
||||
}
|
||||
@ -291,6 +292,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
||||
// DrmSession implementation.
|
||||
|
||||
@Override
|
||||
@DrmSession.State
|
||||
public final int getState() {
|
||||
return state;
|
||||
}
|
||||
|
@ -15,11 +15,21 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.drm;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Thrown when the requested DRM scheme is not supported.
|
||||
*/
|
||||
public final class UnsupportedDrmException extends Exception {
|
||||
|
||||
/**
|
||||
* The reason for the exception.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
|
||||
public @interface Reason {}
|
||||
/**
|
||||
* The requested DRM scheme is unsupported by the device.
|
||||
*/
|
||||
@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception {
|
||||
/**
|
||||
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||
*/
|
||||
@Reason
|
||||
public final int reason;
|
||||
|
||||
/**
|
||||
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||
*/
|
||||
public UnsupportedDrmException(int reason) {
|
||||
public UnsupportedDrmException(@Reason int reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception {
|
||||
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||
* @param cause The cause of this exception.
|
||||
*/
|
||||
public UnsupportedDrmException(int reason, Exception cause) {
|
||||
public UnsupportedDrmException(@Reason int reason, Exception cause) {
|
||||
super(cause);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput {
|
||||
throws IOException, InterruptedException {
|
||||
ensureSpaceForPeek(length);
|
||||
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
|
||||
peekBufferLength += length - bytesPeeked;
|
||||
while (bytesPeeked < length) {
|
||||
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
|
||||
allowEndOfInput);
|
||||
@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput {
|
||||
}
|
||||
}
|
||||
peekBufferPosition += length;
|
||||
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
long offset = extrasHolder.offset;
|
||||
|
||||
// Read the signal byte.
|
||||
scratch.reset(1);
|
||||
readData(offset, scratch.data, 1);
|
||||
offset++;
|
||||
byte signalByte = scratch.data[0];
|
||||
@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
// Read the subsample count, if present.
|
||||
int subsampleCount;
|
||||
if (subsampleEncryption) {
|
||||
scratch.reset(2);
|
||||
readData(offset, scratch.data, 2);
|
||||
offset += 2;
|
||||
scratch.setPosition(0);
|
||||
subsampleCount = scratch.readUnsignedShort();
|
||||
} else {
|
||||
subsampleCount = 1;
|
||||
@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
}
|
||||
if (subsampleEncryption) {
|
||||
int subsampleDataLength = 6 * subsampleCount;
|
||||
ensureCapacity(scratch, subsampleDataLength);
|
||||
scratch.reset(subsampleDataLength);
|
||||
readData(offset, scratch.data, subsampleDataLength);
|
||||
offset += subsampleDataLength;
|
||||
scratch.setPosition(0);
|
||||
@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the passed {@link ParsableByteArray} is of at least the specified limit.
|
||||
*/
|
||||
private static void ensureCapacity(ParsableByteArray byteArray, int limit) {
|
||||
if (byteArray.limit() < limit) {
|
||||
byteArray.reset(new byte[limit], limit);
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the loading thread.
|
||||
|
||||
/**
|
||||
@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||
byte[] encryptionKey) {
|
||||
if (!startWriteOperation()) {
|
||||
infoQueue.commitSampleTimestamp(timeUs);
|
||||
return;
|
||||
@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
|
||||
byte[] encryptionKey) {
|
||||
public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
|
||||
int size, byte[] encryptionKey) {
|
||||
Assertions.checkState(!upstreamFormatRequired);
|
||||
commitSampleTimestamp(timeUs);
|
||||
timesUs[relativeWriteIndex] = timeUs;
|
||||
|
@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||
byte[] encryptionKey) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,7 @@ public interface TrackOutput {
|
||||
* whose metadata is being passed.
|
||||
* @param encryptionKey The encryption key associated with the sample. May be null.
|
||||
*/
|
||||
void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey);
|
||||
void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||
byte[] encryptionKey);
|
||||
|
||||
}
|
||||
|
@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor {
|
||||
private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
|
||||
private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
|
||||
private static final int ID_LANGUAGE = 0x22B59C;
|
||||
private static final int ID_PROJECTION = 0x7670;
|
||||
private static final int ID_PROJECTION_PRIVATE = 0x7672;
|
||||
private static final int ID_STEREO_MODE = 0x53B8;
|
||||
|
||||
private static final int LACING_NONE = 0;
|
||||
private static final int LACING_XIPH = 1;
|
||||
@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
private int[] blockLacingSampleSizes;
|
||||
private int blockTrackNumber;
|
||||
private int blockTrackNumberLength;
|
||||
@C.BufferFlags
|
||||
private int blockFlags;
|
||||
|
||||
// Sample reading state.
|
||||
@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
case ID_CUE_POINT:
|
||||
case ID_CUE_TRACK_POSITIONS:
|
||||
case ID_BLOCK_GROUP:
|
||||
case ID_PROJECTION:
|
||||
return EbmlReader.TYPE_MASTER;
|
||||
case ID_EBML_READ_VERSION:
|
||||
case ID_DOC_TYPE_READ_VERSION:
|
||||
@ -390,6 +395,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
case ID_CUE_TIME:
|
||||
case ID_CUE_CLUSTER_POSITION:
|
||||
case ID_REFERENCE_BLOCK:
|
||||
case ID_STEREO_MODE:
|
||||
return EbmlReader.TYPE_UNSIGNED_INT;
|
||||
case ID_DOC_TYPE:
|
||||
case ID_CODEC_ID:
|
||||
@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
case ID_SIMPLE_BLOCK:
|
||||
case ID_BLOCK:
|
||||
case ID_CODEC_PRIVATE:
|
||||
case ID_PROJECTION_PRIVATE:
|
||||
return EbmlReader.TYPE_BINARY;
|
||||
case ID_DURATION:
|
||||
case ID_SAMPLING_FREQUENCY:
|
||||
@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor {
|
||||
case ID_BLOCK_DURATION:
|
||||
blockDurationUs = scaleTimecodeToUs(value);
|
||||
return;
|
||||
case ID_STEREO_MODE:
|
||||
int layout = (int) value;
|
||||
switch (layout) {
|
||||
case 0:
|
||||
currentTrack.stereoMode = C.STEREO_MODE_MONO;
|
||||
break;
|
||||
case 1:
|
||||
currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor {
|
||||
currentTrack.codecPrivate = new byte[contentSize];
|
||||
input.readFully(currentTrack.codecPrivate, 0, contentSize);
|
||||
return;
|
||||
case ID_PROJECTION_PRIVATE:
|
||||
currentTrack.projectionData = new byte[contentSize];
|
||||
input.readFully(currentTrack.projectionData, 0, contentSize);
|
||||
return;
|
||||
case ID_CONTENT_COMPRESSION_SETTINGS:
|
||||
// This extractor only supports header stripping, so the payload is the stripped bytes.
|
||||
currentTrack.sampleStrippedBytes = new byte[contentSize];
|
||||
@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor {
|
||||
samplePartitionCountRead = true;
|
||||
}
|
||||
int samplePartitionDataSize = samplePartitionCount * 4;
|
||||
if (scratch.limit() < samplePartitionDataSize) {
|
||||
scratch.reset(new byte[samplePartitionDataSize], samplePartitionDataSize);
|
||||
}
|
||||
scratch.reset(samplePartitionDataSize);
|
||||
input.readFully(scratch.data, 0, samplePartitionDataSize);
|
||||
sampleBytesRead += samplePartitionDataSize;
|
||||
scratch.setPosition(0);
|
||||
scratch.setLimit(samplePartitionDataSize);
|
||||
short subsampleCount = (short) (1 + (samplePartitionCount / 2));
|
||||
int subsampleDataSize = 2 + 6 * subsampleCount;
|
||||
if (encryptionSubsampleDataBuffer == null
|
||||
@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor {
|
||||
public int displayWidth = Format.NO_VALUE;
|
||||
public int displayHeight = Format.NO_VALUE;
|
||||
public int displayUnit = DISPLAY_UNIT_PIXELS;
|
||||
public byte[] projectionData = null;
|
||||
@C.StereoMode
|
||||
public int stereoMode = Format.NO_VALUE;
|
||||
|
||||
// Audio elements. Initially set to their default values.
|
||||
public int channelCount = 1;
|
||||
@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
||||
String mimeType;
|
||||
int maxInputSize = Format.NO_VALUE;
|
||||
int pcmEncoding = Format.NO_VALUE;
|
||||
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
|
||||
List<byte[]> initializationData = null;
|
||||
switch (codecId) {
|
||||
case CODEC_ID_VP8:
|
||||
@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor {
|
||||
}
|
||||
|
||||
Format format;
|
||||
int selectionFlags = 0;
|
||||
selectionFlags |= flagDefault ? Format.SELECTION_FLAG_DEFAULT : 0;
|
||||
selectionFlags |= flagForced ? Format.SELECTION_FLAG_FORCED : 0;
|
||||
@C.SelectionFlags int selectionFlags = 0;
|
||||
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
|
||||
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
|
||||
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
|
||||
// into the trackId passed when creating the formats.
|
||||
if (MimeTypes.isAudio(mimeType)) {
|
||||
@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||
}
|
||||
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
|
||||
Format.NO_VALUE, pixelWidthHeightRatio, drmInitData);
|
||||
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData);
|
||||
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||
Format.NO_VALUE, selectionFlags, language, drmInitData);
|
||||
|
@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor {
|
||||
@Override
|
||||
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||
throws IOException, InterruptedException {
|
||||
if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
if (synchronizedHeaderData == 0) {
|
||||
try {
|
||||
synchronize(input, false);
|
||||
} catch (EOFException e) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
}
|
||||
}
|
||||
if (seeker == null) {
|
||||
seeker = setupSeeker(input);
|
||||
@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor {
|
||||
|
||||
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
|
||||
if (sampleBytesRemaining == 0) {
|
||||
if (!maybeResynchronize(extractorInput)) {
|
||||
extractorInput.resetPeekPosition();
|
||||
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
}
|
||||
scratch.setPosition(0);
|
||||
int sampleHeaderData = scratch.readInt();
|
||||
if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK)
|
||||
|| MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
|
||||
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
|
||||
extractorInput.skipFully(1);
|
||||
synchronizedHeaderData = 0;
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
|
||||
if (basisTimeUs == C.TIME_UNSET) {
|
||||
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
|
||||
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
|
||||
@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor {
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary.
|
||||
*/
|
||||
private boolean maybeResynchronize(ExtractorInput extractorInput)
|
||||
throws IOException, InterruptedException {
|
||||
extractorInput.resetPeekPosition();
|
||||
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
scratch.setPosition(0);
|
||||
int sampleHeaderData = scratch.readInt();
|
||||
if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) {
|
||||
int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData);
|
||||
if (frameSize != C.LENGTH_UNSET) {
|
||||
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
synchronizedHeaderData = 0;
|
||||
extractorInput.skipFully(1);
|
||||
return synchronizeCatchingEndOfInput(extractorInput);
|
||||
}
|
||||
|
||||
private boolean synchronizeCatchingEndOfInput(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
// An EOFException will be raised if any peek operation was partially satisfied. If a seek
|
||||
// operation resulted in reading from within the last frame, we may try to peek past the end of
|
||||
// the file in a partially-satisfied read operation, so we need to catch the exception.
|
||||
try {
|
||||
return synchronize(input, false);
|
||||
} catch (EOFException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean synchronize(ExtractorInput input, boolean sniffing)
|
||||
throws IOException, InterruptedException {
|
||||
int searched = 0;
|
||||
int validFrameCount = 0;
|
||||
int candidateSynchronizedHeaderData = 0;
|
||||
int peekedId3Bytes = 0;
|
||||
int searchedBytes = 0;
|
||||
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
|
||||
input.resetPeekPosition();
|
||||
if (input.getPosition() == 0) {
|
||||
Id3Util.parseId3(input, gaplessInfoHolder);
|
||||
@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor {
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (sniffing && searched == MAX_SNIFF_BYTES) {
|
||||
return false;
|
||||
}
|
||||
if (!sniffing && searched == MAX_SYNC_BYTES) {
|
||||
throw new ParserException("Searched too many bytes.");
|
||||
}
|
||||
if (!input.peekFully(scratch.data, 0, 4, true)) {
|
||||
return false;
|
||||
if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
|
||||
// We reached the end of the stream but found at least one valid frame.
|
||||
break;
|
||||
}
|
||||
scratch.setPosition(0);
|
||||
int headerData = scratch.readInt();
|
||||
@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor {
|
||||
if ((candidateSynchronizedHeaderData != 0
|
||||
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|
||||
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
|
||||
// The header is invalid or doesn't match the candidate header. Try the next byte offset.
|
||||
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
|
||||
if (searchedBytes++ == searchLimitBytes) {
|
||||
if (!sniffing) {
|
||||
throw new ParserException("Searched too many bytes.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
validFrameCount = 0;
|
||||
candidateSynchronizedHeaderData = 0;
|
||||
searched++;
|
||||
if (sniffing) {
|
||||
input.resetPeekPosition();
|
||||
input.advancePeekPosition(peekedId3Bytes + searched);
|
||||
input.advancePeekPosition(peekedId3Bytes + searchedBytes);
|
||||
} else {
|
||||
input.skipFully(1);
|
||||
}
|
||||
} else {
|
||||
// The header is valid and matches the candidate header.
|
||||
// The header matches the candidate header and/or is valid.
|
||||
validFrameCount++;
|
||||
if (validFrameCount == 1) {
|
||||
MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
|
||||
@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
}
|
||||
// Prepare to read the synchronized frame.
|
||||
if (sniffing) {
|
||||
input.skipFully(peekedId3Bytes + searched);
|
||||
input.skipFully(peekedId3Bytes + searchedBytes);
|
||||
} else {
|
||||
input.resetPeekPosition();
|
||||
}
|
||||
@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor {
|
||||
|
||||
long position = input.getPosition();
|
||||
long length = input.getLength();
|
||||
int headerData = 0;
|
||||
Seeker seeker = null;
|
||||
|
||||
// Check if there is a Xing header.
|
||||
int xingBase = (synchronizedHeader.version & 1) != 0
|
||||
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
|
||||
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
|
||||
frame.setPosition(xingBase);
|
||||
int headerData = frame.readInt();
|
||||
Seeker seeker = null;
|
||||
if (frame.limit() >= xingBase + 4) {
|
||||
frame.setPosition(xingBase);
|
||||
headerData = frame.readInt();
|
||||
}
|
||||
if (headerData == XING_HEADER || headerData == INFO_HEADER) {
|
||||
seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
|
||||
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
|
||||
@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
|
||||
}
|
||||
input.skipFully(synchronizedHeader.frameSize);
|
||||
} else {
|
||||
} else if (frame.limit() >= 40) {
|
||||
// Check if there is a VBRI header.
|
||||
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
|
||||
headerData = frame.readInt();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
@ -38,6 +39,8 @@ import java.util.List;
|
||||
*/
|
||||
/* package */ final class AtomParsers {
|
||||
|
||||
private static final String TAG = "AtomParsers";
|
||||
|
||||
private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
|
||||
private static final int TYPE_soun = Util.getIntegerCodeForString("soun");
|
||||
private static final int TYPE_text = Util.getIntegerCodeForString("text");
|
||||
@ -248,11 +251,16 @@ import java.util.List;
|
||||
remainingTimestampOffsetChanges--;
|
||||
}
|
||||
|
||||
// Check all the expected samples have been seen.
|
||||
Assertions.checkArgument(remainingSynchronizationSamples == 0);
|
||||
Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0);
|
||||
Assertions.checkArgument(remainingSamplesInChunk == 0);
|
||||
Assertions.checkArgument(remainingTimestampDeltaChanges == 0);
|
||||
// If the stbl's child boxes are not consistent the container is malformed, but the stream may
|
||||
// still be playable.
|
||||
if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0
|
||||
|| remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) {
|
||||
Log.w(TAG, "Inconsistent stbl box for track " + track.id
|
||||
+ ": remainingSynchronizationSamples " + remainingSynchronizationSamples
|
||||
+ ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta
|
||||
+ ", remainingSamplesInChunk " + remainingSamplesInChunk
|
||||
+ ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges);
|
||||
}
|
||||
} else {
|
||||
long[] chunkOffsetsBytes = new long[chunkIterator.length];
|
||||
int[] chunkSampleCounts = new int[chunkIterator.length];
|
||||
@ -636,7 +644,7 @@ import java.util.List;
|
||||
0 /* subsample timing is absolute */);
|
||||
} else if (childAtomType == Atom.TYPE_c608) {
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, 0, language, drmInitData);
|
||||
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData);
|
||||
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
@ -665,6 +673,7 @@ import java.util.List;
|
||||
List<byte[]> initializationData = null;
|
||||
String mimeType = null;
|
||||
byte[] projectionData = null;
|
||||
@C.StereoMode
|
||||
int stereoMode = Format.NO_VALUE;
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
@ -889,7 +898,7 @@ import java.util.List;
|
||||
|
||||
if (out.format == null && mimeType != null) {
|
||||
// TODO: Determine the correct PCM encoding.
|
||||
int pcmEncoding =
|
||||
@C.PcmEncoding int pcmEncoding =
|
||||
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
|
||||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
||||
@ -1169,6 +1178,7 @@ import java.util.List;
|
||||
|
||||
public Format format;
|
||||
public int nalUnitLengthFieldLength;
|
||||
@Track.Transformation
|
||||
public int requiredSampleTransformation;
|
||||
|
||||
public StsdData(int numberOfEntries) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -64,6 +67,13 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
private static final String TAG = "FragmentedMp4Extractor";
|
||||
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
|
||||
|
||||
/**
|
||||
* Flags controlling the behavior of the extractor.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
|
||||
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED})
|
||||
public @interface Flags {}
|
||||
/**
|
||||
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
|
||||
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
|
||||
@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
* This flag does nothing if the stream is not a video stream.
|
||||
*/
|
||||
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
||||
|
||||
/**
|
||||
* Flag to ignore any tfdt boxes in the stream.
|
||||
*/
|
||||
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
|
||||
|
||||
/**
|
||||
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
|
||||
* container.
|
||||
@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
|
||||
|
||||
// Workarounds.
|
||||
@Flags
|
||||
private final int flags;
|
||||
private final Track sideloadedTrack;
|
||||
|
||||
@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param flags Flags to allow parsing of faulty streams.
|
||||
* @param flags Flags that control the extractor's behavior.
|
||||
*/
|
||||
public FragmentedMp4Extractor(int flags) {
|
||||
public FragmentedMp4Extractor(@Flags int flags) {
|
||||
this(flags, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param flags Flags to allow parsing of faulty streams.
|
||||
* @param flags Flags that control the extractor's behavior.
|
||||
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
|
||||
* will not receive a moov box in the input data.
|
||||
*/
|
||||
public FragmentedMp4Extractor(int flags, Track sideloadedTrack) {
|
||||
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) {
|
||||
this.sideloadedTrack = sideloadedTrack;
|
||||
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
||||
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
|
||||
@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
|
||||
int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||
int moofContainerChildrenSize = moof.containerChildren.size();
|
||||
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
||||
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
||||
@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
* Parses a traf atom (defined in 14496-12).
|
||||
*/
|
||||
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
||||
int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
||||
if (trackBundle == null) {
|
||||
@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
|
||||
int flags) {
|
||||
@Flags int flags) {
|
||||
int trunCount = 0;
|
||||
int totalSampleCount = 0;
|
||||
List<LeafAtom> leafChildren = traf.leafChildren;
|
||||
@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
* @param trun The trun atom to decode.
|
||||
* @return The starting position of samples for the next run.
|
||||
*/
|
||||
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags,
|
||||
ParsableByteArray trun, int trackRunStart) {
|
||||
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
|
||||
@Flags int flags, ParsableByteArray trun, int trackRunStart) {
|
||||
trun.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = trun.readInt();
|
||||
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
||||
@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||
int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
|
||||
@C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
|
||||
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
|
||||
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
|
||||
byte[] encryptionKey = null;
|
||||
|
@ -100,13 +100,14 @@ import java.io.IOException;
|
||||
while (bytesSearched < bytesToSearch) {
|
||||
// Read an atom header.
|
||||
int headerSize = Atom.HEADER_SIZE;
|
||||
buffer.reset(headerSize);
|
||||
input.peekFully(buffer.data, 0, headerSize);
|
||||
buffer.setPosition(0);
|
||||
long atomSize = buffer.readUnsignedInt();
|
||||
int atomType = buffer.readInt();
|
||||
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||
headerSize = Atom.LONG_HEADER_SIZE;
|
||||
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
|
||||
buffer.setLimit(Atom.LONG_HEADER_SIZE);
|
||||
atomSize = buffer.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
@ -139,9 +140,7 @@ import java.io.IOException;
|
||||
if (atomDataSize < 8) {
|
||||
return false;
|
||||
}
|
||||
if (buffer.capacity() < atomDataSize) {
|
||||
buffer.reset(new byte[atomDataSize], atomDataSize);
|
||||
}
|
||||
buffer.reset(atomDataSize);
|
||||
input.peekFully(buffer.data, 0, atomDataSize);
|
||||
int brandsCount = atomDataSize / 4;
|
||||
for (int i = 0; i < brandsCount; i++) {
|
||||
|
@ -15,14 +15,23 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Encapsulates information describing an MP4 track.
|
||||
*/
|
||||
public final class Track {
|
||||
|
||||
/**
|
||||
* The transformation to apply to samples in the track, if any.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})
|
||||
public @interface Transformation {}
|
||||
/**
|
||||
* A no-op sample transformation.
|
||||
*/
|
||||
@ -66,6 +75,7 @@ public final class Track {
|
||||
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
|
||||
* sample.
|
||||
*/
|
||||
@Transformation
|
||||
public final int sampleTransformation;
|
||||
|
||||
/**
|
||||
@ -90,7 +100,7 @@ public final class Track {
|
||||
public final int nalUnitLengthFieldLength;
|
||||
|
||||
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
|
||||
Format format, int sampleTransformation,
|
||||
Format format, @Transformation int sampleTransformation,
|
||||
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
|
||||
long[] editListDurations, long[] editListMediaTimes) {
|
||||
this.id = id;
|
||||
|
@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
public final int[] flags;
|
||||
|
||||
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
|
||||
public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs,
|
||||
int[] flags) {
|
||||
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||
|
@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Extracts EIA-608 data from a RawCC file
|
||||
* Extracts CEA data from a RawCC file.
|
||||
*/
|
||||
public final class RawCcExtractor implements Extractor {
|
||||
|
||||
@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor {
|
||||
trackOutput = extractorOutput.track(0);
|
||||
extractorOutput.endTracks();
|
||||
|
||||
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608,
|
||||
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
|
||||
null, Format.NO_VALUE, 0, null, null));
|
||||
}
|
||||
|
||||
@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor {
|
||||
dataScratch.reset();
|
||||
input.readFully(dataScratch.data, 0, 3);
|
||||
|
||||
// only accept EIA-608 packets which have validity (6th bit) == 1 and
|
||||
// type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100
|
||||
int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07;
|
||||
if (ccValidityAndType == 0x04) {
|
||||
trackOutput.sampleData(dataScratch, 2);
|
||||
sampleBytesWritten += 2;
|
||||
}
|
||||
trackOutput.sampleData(dataScratch, 3);
|
||||
sampleBytesWritten += 3;
|
||||
}
|
||||
|
||||
if (sampleBytesWritten > 0) {
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.ts;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.util.SparseBooleanArray;
|
||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Default implementation for {@link ElementaryStreamReader.Factory}.
|
||||
*/
|
||||
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
|
||||
|
||||
/**
|
||||
* Flags controlling what workarounds are enabled for elementary stream readers.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM,
|
||||
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE})
|
||||
public @interface WorkaroundFlags {
|
||||
}
|
||||
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
||||
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
||||
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
||||
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
|
||||
public static final int WORKAROUND_MAP_BY_TYPE = 16;
|
||||
|
||||
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1.
|
||||
|
||||
private final SparseBooleanArray trackIds;
|
||||
@WorkaroundFlags
|
||||
private final int workaroundFlags;
|
||||
private Id3Reader id3Reader;
|
||||
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
|
||||
|
||||
public DefaultStreamReaderFactory() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public DefaultStreamReaderFactory(int workaroundFlags) {
|
||||
trackIds = new SparseBooleanArray();
|
||||
this.workaroundFlags = workaroundFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
|
||||
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
|
||||
|
||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
|
||||
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
||||
// appears intermittently during playback. See b/20261500.
|
||||
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
|
||||
}
|
||||
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
|
||||
if (trackIds.get(trackId)) {
|
||||
return null;
|
||||
}
|
||||
trackIds.put(trackId, true);
|
||||
switch (streamType) {
|
||||
case TsExtractor.TS_STREAM_TYPE_MPA:
|
||||
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
||||
return new MpegAudioReader(output.track(trackId), esInfo.language);
|
||||
case TsExtractor.TS_STREAM_TYPE_AAC:
|
||||
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
|
||||
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
|
||||
case TsExtractor.TS_STREAM_TYPE_AC3:
|
||||
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
||||
return new Ac3Reader(output.track(trackId), esInfo.language);
|
||||
case TsExtractor.TS_STREAM_TYPE_DTS:
|
||||
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
||||
return new DtsReader(output.track(trackId), esInfo.language);
|
||||
case TsExtractor.TS_STREAM_TYPE_H262:
|
||||
return new H262Reader(output.track(trackId));
|
||||
case TsExtractor.TS_STREAM_TYPE_H264:
|
||||
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0
|
||||
? null : new H264Reader(output.track(trackId),
|
||||
new SeiReader(output.track(nextEmbeddedTrackId++)),
|
||||
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
||||
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
|
||||
case TsExtractor.TS_STREAM_TYPE_H265:
|
||||
return new H265Reader(output.track(trackId),
|
||||
new SeiReader(output.track(nextEmbeddedTrackId++)));
|
||||
case TsExtractor.TS_STREAM_TYPE_ID3:
|
||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
|
||||
return id3Reader;
|
||||
} else {
|
||||
return new Id3Reader(output.track(nextEmbeddedTrackId++));
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,13 +15,60 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.ts;
|
||||
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
||||
/**
|
||||
* Extracts individual samples from an elementary media stream, preserving original order.
|
||||
*/
|
||||
/* package */ abstract class ElementaryStreamReader {
|
||||
public abstract class ElementaryStreamReader {
|
||||
|
||||
/**
|
||||
* Factory of {@link ElementaryStreamReader} instances.
|
||||
*/
|
||||
public interface Factory {
|
||||
|
||||
/**
|
||||
* Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the
|
||||
* stream type is not supported or if the stream already has a reader assigned to it.
|
||||
*
|
||||
* @param pid The pid for the PMT entry.
|
||||
* @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining
|
||||
* the type of the stream.
|
||||
* @param esInfo The descriptor information linked to the elementary stream.
|
||||
* @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the
|
||||
* created readers.
|
||||
* @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided
|
||||
* pid. {@code null} if the stream is not supported or if it should be ignored.
|
||||
*/
|
||||
ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo,
|
||||
ExtractorOutput output);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds descriptor information associated with an elementary stream.
|
||||
*/
|
||||
public static final class EsInfo {
|
||||
|
||||
public final int streamType;
|
||||
public String language;
|
||||
public byte[] descriptorBytes;
|
||||
|
||||
/**
|
||||
* @param streamType The type of the stream as defined by the
|
||||
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
|
||||
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
|
||||
* @param descriptorBytes The descriptor bytes associated to the stream.
|
||||
*/
|
||||
public EsInfo(int streamType, String language, byte[] descriptorBytes) {
|
||||
this.streamType = streamType;
|
||||
this.language = language;
|
||||
this.descriptorBytes = descriptorBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final TrackOutput output;
|
||||
|
||||
|
@ -128,7 +128,7 @@ import java.util.Collections;
|
||||
if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) {
|
||||
int bytesWrittenPastStartCode = limit - startCodeOffset;
|
||||
if (foundFirstFrameInGroup) {
|
||||
int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
@C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode;
|
||||
output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null);
|
||||
isKeyframe = false;
|
||||
@ -136,7 +136,7 @@ import java.util.Collections;
|
||||
if (startCodeValue == START_GROUP) {
|
||||
foundFirstFrameInGroup = false;
|
||||
isKeyframe = true;
|
||||
} else /* startCode == START_PICTURE */ {
|
||||
} else /* startCodeValue == START_PICTURE */ {
|
||||
frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs);
|
||||
framePosition = totalBytesWritten - bytesWrittenPastStartCode;
|
||||
pesPtsUsAvailable = false;
|
||||
|
@ -57,7 +57,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* @param output A {@link TrackOutput} to which H.264 samples should be written.
|
||||
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
||||
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
|
||||
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
|
||||
* synchronization samples (key-frames).
|
||||
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
|
||||
@ -420,7 +420,7 @@ import java.util.List;
|
||||
}
|
||||
|
||||
private void outputSample(int offset) {
|
||||
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
int size = (int) (nalUnitStartPosition - samplePosition);
|
||||
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @param output A {@link TrackOutput} to which H.265 samples should be written.
|
||||
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
||||
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
|
||||
*/
|
||||
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
||||
super(output);
|
||||
@ -471,7 +471,7 @@ import java.util.Collections;
|
||||
}
|
||||
|
||||
private void outputSample(int offset) {
|
||||
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
int size = (int) (nalUnitStartPosition - samplePosition);
|
||||
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
||||
}
|
||||
|
@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor {
|
||||
input.peekFully(psPacketBuffer.data, 0, 10);
|
||||
|
||||
// We only care about the pack_stuffing_length in here, skip the first 77 bits.
|
||||
psPacketBuffer.setPosition(0);
|
||||
psPacketBuffer.skipBytes(9);
|
||||
psPacketBuffer.setPosition(9);
|
||||
|
||||
// Last 3 bits is the length.
|
||||
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
|
||||
@ -209,7 +208,7 @@ public final class PsExtractor implements Extractor {
|
||||
}
|
||||
}
|
||||
|
||||
// The next 2 bytes are the length, once we have that we can consume the complete packet.
|
||||
// The next 2 bytes are the length. Once we have that we can consume the complete packet.
|
||||
input.peekFully(psPacketBuffer.data, 0, 2);
|
||||
psPacketBuffer.setPosition(0);
|
||||
int payloadLength = psPacketBuffer.readUnsignedShort();
|
||||
@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor {
|
||||
// Just skip this data.
|
||||
input.skipFully(pesLength);
|
||||
} else {
|
||||
if (psPacketBuffer.capacity() < pesLength) {
|
||||
// Reallocate for this and future packets.
|
||||
psPacketBuffer.reset(new byte[pesLength], pesLength);
|
||||
}
|
||||
psPacketBuffer.reset(pesLength);
|
||||
// Read the whole packet and the header for consumption.
|
||||
input.readFully(psPacketBuffer.data, 0, pesLength);
|
||||
psPacketBuffer.setPosition(6);
|
||||
psPacketBuffer.setLimit(pesLength);
|
||||
payloadReader.consume(psPacketBuffer);
|
||||
psPacketBuffer.setLimit(psPacketBuffer.capacity());
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.text.eia608.Eia608Decoder;
|
||||
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
||||
/**
|
||||
* Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}.
|
||||
* Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
|
||||
*/
|
||||
/* package */ final class SeiReader {
|
||||
|
||||
@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
||||
public SeiReader(TrackOutput output) {
|
||||
this.output = output;
|
||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, null,
|
||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
|
||||
Format.NO_VALUE, 0, null, null));
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
payloadSize += b;
|
||||
} while (b == 0xFF);
|
||||
// Process the payload.
|
||||
if (Eia608Decoder.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
||||
if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
|
||||
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
|
||||
// + user_data_type_code (1).
|
||||
seiBuffer.skipBytes(8);
|
||||
@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
seiBuffer.skipBytes(1);
|
||||
int sampleBytes = 0;
|
||||
for (int i = 0; i < ccCount; i++) {
|
||||
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07;
|
||||
// Check that validity == 1 and type == 0.
|
||||
int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07;
|
||||
// Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1).
|
||||
if (ccValidityAndType != 0x04) {
|
||||
seiBuffer.skipBytes(2);
|
||||
seiBuffer.skipBytes(3);
|
||||
} else {
|
||||
sampleBytes += 2;
|
||||
output.sampleData(seiBuffer, 2);
|
||||
sampleBytes += 3;
|
||||
output.sampleData(seiBuffer, 3);
|
||||
}
|
||||
}
|
||||
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);
|
||||
|
@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
@ -32,6 +30,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||
@ -50,30 +49,24 @@ public final class TsExtractor implements Extractor {
|
||||
|
||||
};
|
||||
|
||||
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
||||
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
||||
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
||||
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
|
||||
public static final int WORKAROUND_MAP_BY_TYPE = 16;
|
||||
|
||||
private static final String TAG = "TsExtractor";
|
||||
|
||||
private static final int TS_PACKET_SIZE = 188;
|
||||
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
|
||||
private static final int TS_PAT_PID = 0;
|
||||
|
||||
private static final int TS_STREAM_TYPE_MPA = 0x03;
|
||||
private static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
|
||||
private static final int TS_STREAM_TYPE_AAC = 0x0F;
|
||||
private static final int TS_STREAM_TYPE_AC3 = 0x81;
|
||||
private static final int TS_STREAM_TYPE_DTS = 0x8A;
|
||||
private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
||||
private static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
||||
private static final int TS_STREAM_TYPE_H262 = 0x02;
|
||||
private static final int TS_STREAM_TYPE_H264 = 0x1B;
|
||||
private static final int TS_STREAM_TYPE_H265 = 0x24;
|
||||
private static final int TS_STREAM_TYPE_ID3 = 0x15;
|
||||
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1
|
||||
public static final int TS_STREAM_TYPE_MPA = 0x03;
|
||||
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
|
||||
public static final int TS_STREAM_TYPE_AAC = 0x0F;
|
||||
public static final int TS_STREAM_TYPE_AC3 = 0x81;
|
||||
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
||||
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
||||
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
||||
public static final int TS_STREAM_TYPE_H262 = 0x02;
|
||||
public static final int TS_STREAM_TYPE_H264 = 0x1B;
|
||||
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
||||
public static final int TS_STREAM_TYPE_ID3 = 0x15;
|
||||
|
||||
|
||||
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
|
||||
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
|
||||
@ -83,35 +76,38 @@ public final class TsExtractor implements Extractor {
|
||||
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
|
||||
|
||||
private final TimestampAdjuster timestampAdjuster;
|
||||
private final int workaroundFlags;
|
||||
private final ParsableByteArray tsPacketBuffer;
|
||||
private final ParsableBitArray tsScratch;
|
||||
private final SparseIntArray continuityCounters;
|
||||
private final ElementaryStreamReader.Factory streamReaderFactory;
|
||||
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||
/* package */ final SparseBooleanArray trackIds;
|
||||
|
||||
// Accessed only by the loading thread.
|
||||
private ExtractorOutput output;
|
||||
private int nextEmbeddedTrackId;
|
||||
/* package */ Id3Reader id3Reader;
|
||||
|
||||
public TsExtractor() {
|
||||
this(new TimestampAdjuster(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||
*/
|
||||
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
||||
this(timestampAdjuster, 0);
|
||||
this(timestampAdjuster, new DefaultStreamReaderFactory());
|
||||
}
|
||||
|
||||
public TsExtractor(TimestampAdjuster timestampAdjuster, int workaroundFlags) {
|
||||
/**
|
||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||
* @param customReaderFactory Factory for injecting a custom set of elementary stream readers.
|
||||
*/
|
||||
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
||||
ElementaryStreamReader.Factory customReaderFactory) {
|
||||
this.timestampAdjuster = timestampAdjuster;
|
||||
this.workaroundFlags = workaroundFlags;
|
||||
this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
|
||||
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
||||
tsScratch = new ParsableBitArray(new byte[3]);
|
||||
tsPayloadReaders = new SparseArray<>();
|
||||
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
|
||||
trackIds = new SparseBooleanArray();
|
||||
nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
|
||||
continuityCounters = new SparseIntArray();
|
||||
}
|
||||
|
||||
@ -416,12 +412,6 @@ public final class TsExtractor implements Extractor {
|
||||
// Skip the descriptors.
|
||||
sectionData.skipBytes(programInfoLength);
|
||||
|
||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
|
||||
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
||||
// appears intermittently during playback. See b/20261500.
|
||||
id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3));
|
||||
}
|
||||
|
||||
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
|
||||
- programInfoLength - 4 /* CRC length */;
|
||||
while (remainingEntriesLength > 0) {
|
||||
@ -431,63 +421,15 @@ public final class TsExtractor implements Extractor {
|
||||
int elementaryPid = pmtScratch.readBits(13);
|
||||
pmtScratch.skipBits(4); // reserved
|
||||
int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
|
||||
EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
|
||||
ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
|
||||
if (streamType == 0x06) {
|
||||
streamType = esInfo.streamType;
|
||||
}
|
||||
remainingEntriesLength -= esInfoLength + 5;
|
||||
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid;
|
||||
if (trackIds.get(trackId)) {
|
||||
continue;
|
||||
}
|
||||
ElementaryStreamReader pesPayloadReader;
|
||||
switch (streamType) {
|
||||
case TS_STREAM_TYPE_MPA:
|
||||
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
|
||||
break;
|
||||
case TS_STREAM_TYPE_MPA_LSF:
|
||||
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
|
||||
break;
|
||||
case TS_STREAM_TYPE_AAC:
|
||||
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
|
||||
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
|
||||
break;
|
||||
case TS_STREAM_TYPE_AC3:
|
||||
case TS_STREAM_TYPE_E_AC3:
|
||||
pesPayloadReader = new Ac3Reader(output.track(trackId), esInfo.language);
|
||||
break;
|
||||
case TS_STREAM_TYPE_DTS:
|
||||
case TS_STREAM_TYPE_HDMV_DTS:
|
||||
pesPayloadReader = new DtsReader(output.track(trackId), esInfo.language);
|
||||
break;
|
||||
case TS_STREAM_TYPE_H262:
|
||||
pesPayloadReader = new H262Reader(output.track(trackId));
|
||||
break;
|
||||
case TS_STREAM_TYPE_H264:
|
||||
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
|
||||
: new H264Reader(output.track(trackId),
|
||||
new SeiReader(output.track(nextEmbeddedTrackId++)),
|
||||
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
||||
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
|
||||
break;
|
||||
case TS_STREAM_TYPE_H265:
|
||||
pesPayloadReader = new H265Reader(output.track(trackId),
|
||||
new SeiReader(output.track(nextEmbeddedTrackId++)));
|
||||
break;
|
||||
case TS_STREAM_TYPE_ID3:
|
||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
|
||||
pesPayloadReader = id3Reader;
|
||||
} else {
|
||||
pesPayloadReader = new Id3Reader(output.track(nextEmbeddedTrackId++));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pesPayloadReader = null;
|
||||
break;
|
||||
}
|
||||
ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid,
|
||||
streamType, esInfo, output);
|
||||
|
||||
if (pesPayloadReader != null) {
|
||||
trackIds.put(trackId, true);
|
||||
tsPayloadReaders.put(elementaryPid,
|
||||
new PesReader(pesPayloadReader, timestampAdjuster));
|
||||
}
|
||||
@ -497,18 +439,17 @@ public final class TsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream info read from the available descriptors, or -1 if no
|
||||
* descriptors are present. Sets {@code data}'s position to the end of the descriptors.
|
||||
* Returns the stream info read from the available descriptors. Sets {@code data}'s position to
|
||||
* the end of the descriptors.
|
||||
*
|
||||
* @param data A buffer with its position set to the start of the first descriptor.
|
||||
* @param length The length of descriptors to read from the current position in {@code data}.
|
||||
* @return The stream info read from the available descriptors, or -1 if no
|
||||
* descriptors are present.
|
||||
* @return The stream info read from the available descriptors.
|
||||
*/
|
||||
private EsInfo readEsInfo(ParsableByteArray data, int length) {
|
||||
int descriptorsEndPosition = data.getPosition() + length;
|
||||
private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) {
|
||||
int descriptorsStartPosition = data.getPosition();
|
||||
int descriptorsEndPosition = descriptorsStartPosition + length;
|
||||
int streamType = -1;
|
||||
int audioType = -1;
|
||||
String language = null;
|
||||
while (data.getPosition() < descriptorsEndPosition) {
|
||||
int descriptorTag = data.readUnsignedByte();
|
||||
@ -531,27 +472,14 @@ public final class TsExtractor implements Extractor {
|
||||
streamType = TS_STREAM_TYPE_DTS;
|
||||
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
||||
language = new String(data.data, data.getPosition(), 3).trim();
|
||||
audioType = data.data[data.getPosition() + 3];
|
||||
// Audio type is ignored.
|
||||
}
|
||||
// Skip unused bytes of current descriptor.
|
||||
data.skipBytes(positionOfNextDescriptor - data.getPosition());
|
||||
}
|
||||
data.setPosition(descriptorsEndPosition);
|
||||
return new EsInfo(streamType, audioType, language);
|
||||
}
|
||||
|
||||
private final class EsInfo {
|
||||
|
||||
final int streamType;
|
||||
final int audioType;
|
||||
final String language;
|
||||
|
||||
public EsInfo(int streamType, int audioType, String language) {
|
||||
this.streamType = streamType;
|
||||
this.audioType = audioType;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
return new ElementaryStreamReader.EsInfo(streamType, language,
|
||||
Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C;
|
||||
/** Bits per sample for the audio data. */
|
||||
private final int bitsPerSample;
|
||||
/** The PCM encoding */
|
||||
@C.PcmEncoding
|
||||
private final int encoding;
|
||||
|
||||
/** Offset to the start of sample data. */
|
||||
@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C;
|
||||
private long dataSize;
|
||||
|
||||
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
|
||||
int bitsPerSample, int encoding) {
|
||||
int bitsPerSample, @C.PcmEncoding int encoding) {
|
||||
this.numChannels = numChannels;
|
||||
this.sampleRateHz = sampleRateHz;
|
||||
this.averageBytesPerSecond = averageBytesPerSecond;
|
||||
@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C;
|
||||
}
|
||||
|
||||
/** Returns the PCM encoding. **/
|
||||
@C.PcmEncoding
|
||||
public int getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ import java.io.IOException;
|
||||
+ blockAlignment);
|
||||
}
|
||||
|
||||
int encoding = Util.getPcmEncoding(bitsPerSample);
|
||||
@C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample);
|
||||
if (encoding == C.ENCODING_INVALID) {
|
||||
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
|
||||
return null;
|
||||
|
@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
MediaCrypto mediaCrypto = null;
|
||||
boolean drmSessionRequiresSecureDecoder = false;
|
||||
if (drmSession != null) {
|
||||
int drmSessionState = drmSession.getState();
|
||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
||||
@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
if (drmSession == null) {
|
||||
return false;
|
||||
}
|
||||
int drmSessionState = drmSession.getState();
|
||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
int sourceIndex = timeline.getSourceIndexForPeriod(index);
|
||||
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, callback,
|
||||
allocator, positionUs);
|
||||
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator,
|
||||
positionUs);
|
||||
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
|
||||
return mediaPeriod;
|
||||
}
|
||||
|
@ -61,12 +61,15 @@ import java.util.Arrays;
|
||||
private final Handler eventHandler;
|
||||
private final ExtractorMediaSource.EventListener eventListener;
|
||||
private final MediaSource.Listener sourceListener;
|
||||
private final Callback callback;
|
||||
private final Allocator allocator;
|
||||
private final Loader loader;
|
||||
private final ExtractorHolder extractorHolder;
|
||||
private final ConditionVariable loadCondition;
|
||||
private final Runnable maybeFinishPrepareRunnable;
|
||||
private final Runnable onContinueLoadingRequestedRunnable;
|
||||
private final Handler handler;
|
||||
|
||||
private Callback callback;
|
||||
private SeekMap seekMap;
|
||||
private boolean tracksBuilt;
|
||||
private boolean prepared;
|
||||
@ -85,6 +88,7 @@ import java.util.Arrays;
|
||||
|
||||
private int extractedSamplesCountAtStartOfLoad;
|
||||
private boolean loadingFinished;
|
||||
private boolean released;
|
||||
|
||||
/**
|
||||
* @param uri The {@link Uri} of the media stream.
|
||||
@ -94,30 +98,41 @@ import java.util.Arrays;
|
||||
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param sourceListener A listener to notify when the timeline has been loaded.
|
||||
* @param callback A callback to receive updates from the period.
|
||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
*/
|
||||
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
|
||||
int minLoadableRetryCount, Handler eventHandler,
|
||||
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener,
|
||||
Callback callback, Allocator allocator) {
|
||||
Allocator allocator) {
|
||||
this.uri = uri;
|
||||
this.dataSource = dataSource;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.sourceListener = sourceListener;
|
||||
this.callback = callback;
|
||||
this.allocator = allocator;
|
||||
loader = new Loader("Loader:ExtractorMediaPeriod");
|
||||
extractorHolder = new ExtractorHolder(extractors, this);
|
||||
loadCondition = new ConditionVariable();
|
||||
maybeFinishPrepareRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
maybeFinishPrepare();
|
||||
}
|
||||
};
|
||||
onContinueLoadingRequestedRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!released) {
|
||||
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
handler = new Handler();
|
||||
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
sampleQueues = new DefaultTrackOutput[0];
|
||||
length = C.LENGTH_UNSET;
|
||||
loadCondition.open();
|
||||
startLoading();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
@ -126,11 +141,20 @@ import java.util.Arrays;
|
||||
@Override
|
||||
public void run() {
|
||||
extractorHolder.release();
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
sampleQueue.disable();
|
||||
}
|
||||
}
|
||||
});
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
sampleQueue.disable();
|
||||
}
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
released = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
this.callback = callback;
|
||||
loadCondition.open();
|
||||
startLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -330,7 +354,7 @@ import java.util.Arrays;
|
||||
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
|
||||
}
|
||||
|
||||
// ExtractorOutput implementation.
|
||||
// ExtractorOutput implementation. Called by the loading thread.
|
||||
|
||||
@Override
|
||||
public TrackOutput track(int id) {
|
||||
@ -344,26 +368,26 @@ import java.util.Arrays;
|
||||
@Override
|
||||
public void endTracks() {
|
||||
tracksBuilt = true;
|
||||
maybeFinishPrepare();
|
||||
handler.post(maybeFinishPrepareRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekMap(SeekMap seekMap) {
|
||||
this.seekMap = seekMap;
|
||||
maybeFinishPrepare();
|
||||
handler.post(maybeFinishPrepareRunnable);
|
||||
}
|
||||
|
||||
// UpstreamFormatChangedListener implementation
|
||||
// UpstreamFormatChangedListener implementation. Called by the loading thread.
|
||||
|
||||
@Override
|
||||
public void onUpstreamFormatChanged(Format format) {
|
||||
maybeFinishPrepare();
|
||||
handler.post(maybeFinishPrepareRunnable);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void maybeFinishPrepare() {
|
||||
if (prepared || seekMap == null || !tracksBuilt) {
|
||||
if (released || prepared || seekMap == null || !tracksBuilt) {
|
||||
return;
|
||||
}
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
@ -576,7 +600,7 @@ import java.util.Arrays;
|
||||
if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) {
|
||||
position = input.getPosition();
|
||||
loadCondition.close();
|
||||
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this);
|
||||
handler.post(onContinueLoadingRequestedRunnable);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
Assertions.checkArgument(index == 0);
|
||||
return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(),
|
||||
extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener,
|
||||
this, callback, allocator);
|
||||
this, allocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,7 +19,6 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.IOException;
|
||||
@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs);
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
return childSource.createPeriod(index % childPeriodCount, allocator, positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
/**
|
||||
* Called when preparation completes.
|
||||
* <p>
|
||||
* May be called from any thread. After invoking this method, the {@link MediaPeriod} can expect
|
||||
* Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect
|
||||
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
|
||||
* called with the initial track selection.
|
||||
*
|
||||
@ -42,6 +42,17 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this media period asynchronously.
|
||||
* <p>
|
||||
* {@code callback.onPrepared} is called when preparation completes. If preparation fails,
|
||||
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
|
||||
*
|
||||
* @param callback Callback to receive updates from this period, including being notified when
|
||||
* preparation completes.
|
||||
*/
|
||||
void prepare(Callback callback);
|
||||
|
||||
/**
|
||||
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such
|
||||
* error exists.
|
||||
|
@ -16,7 +16,6 @@
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import java.io.IOException;
|
||||
|
||||
@ -54,18 +53,13 @@ public interface MediaSource {
|
||||
|
||||
/**
|
||||
* Returns a {@link MediaPeriod} corresponding to the period at the specified index.
|
||||
* <p>
|
||||
* {@link Callback#onPrepared(MediaPeriod)} is called when the new period is prepared. If
|
||||
* preparation fails, {@link MediaPeriod#maybeThrowPrepareError()} will throw an
|
||||
* {@link IOException} if called on the returned instance.
|
||||
*
|
||||
* @param index The index of the period.
|
||||
* @param callback A callback to receive updates from the period.
|
||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
* @param positionUs The player's current playback position.
|
||||
* @return A new {@link MediaPeriod}.
|
||||
*/
|
||||
MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, long positionUs);
|
||||
MediaPeriod createPeriod(int index, Allocator allocator, long positionUs);
|
||||
|
||||
/**
|
||||
* Releases the period.
|
||||
|
@ -28,20 +28,27 @@ import java.util.IdentityHashMap;
|
||||
|
||||
public final MediaPeriod[] periods;
|
||||
|
||||
private final Callback callback;
|
||||
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
|
||||
|
||||
private Callback callback;
|
||||
private int pendingChildPrepareCount;
|
||||
private TrackGroupArray trackGroups;
|
||||
|
||||
private MediaPeriod[] enabledPeriods;
|
||||
private SequenceableLoader sequenceableLoader;
|
||||
|
||||
public MergingMediaPeriod(Callback callback, MediaPeriod... periods) {
|
||||
public MergingMediaPeriod(MediaPeriod... periods) {
|
||||
this.periods = periods;
|
||||
this.callback = callback;
|
||||
streamPeriodIndices = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
this.callback = callback;
|
||||
pendingChildPrepareCount = periods.length;
|
||||
for (MediaPeriod period : periods) {
|
||||
period.prepare(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,11 +15,12 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -36,11 +37,16 @@ public final class MergingMediaSource implements MediaSource {
|
||||
*/
|
||||
public static final class IllegalMergeException extends IOException {
|
||||
|
||||
/**
|
||||
* The reason the merge failed.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH})
|
||||
public @interface Reason {}
|
||||
/**
|
||||
* The merge failed because one of the sources being merged has a dynamic window.
|
||||
*/
|
||||
public static final int REASON_WINDOWS_ARE_DYNAMIC = 0;
|
||||
|
||||
/**
|
||||
* The merge failed because the sources have different period counts.
|
||||
*/
|
||||
@ -50,13 +56,14 @@ public final class MergingMediaSource implements MediaSource {
|
||||
* The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
||||
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
||||
*/
|
||||
@Reason
|
||||
public final int reason;
|
||||
|
||||
/**
|
||||
* @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
||||
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
||||
*/
|
||||
public IllegalMergeException(int reason) {
|
||||
public IllegalMergeException(@Reason int reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
|
||||
// The periods are only referenced after they have all been prepared.
|
||||
MergingMediaPeriod mergingPeriod = new MergingMediaPeriod(callback, periods);
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
periods[i] = mediaSources[i].createPeriod(index, mergingPeriod, allocator, positionUs);
|
||||
Assertions.checkState(periods[i] != null, "Child source must not return null period");
|
||||
periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs);
|
||||
}
|
||||
return mergingPeriod;
|
||||
return new MergingMediaPeriod(periods);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,7 +29,7 @@ public interface SequenceableLoader {
|
||||
|
||||
/**
|
||||
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method
|
||||
* to be called when it can continue to load data.
|
||||
* to be called when it can continue to load data. Called on the playback thread.
|
||||
*/
|
||||
void onContinueLoadingRequested(T source);
|
||||
|
||||
|
@ -78,6 +78,11 @@ import java.util.Arrays;
|
||||
loader.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
loader.maybeThrowError();
|
||||
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
Assertions.checkArgument(index == 0);
|
||||
MediaPeriod mediaPeriod = new SingleSampleMediaPeriod(uri, dataSourceFactory, format,
|
||||
minLoadableRetryCount, eventHandler, eventListener, eventSourceId);
|
||||
callback.onPrepared(mediaPeriod);
|
||||
return mediaPeriod;
|
||||
return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount,
|
||||
eventHandler, eventListener, eventSourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.chunk;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||
byte[] encryptionKey) {
|
||||
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||
byte[] encryptionKey) {
|
||||
throw new IllegalStateException("Unexpected sample data in initialization chunk");
|
||||
}
|
||||
|
||||
|
@ -48,10 +48,10 @@ import java.util.List;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final long elapsedRealtimeOffset;
|
||||
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
||||
private final Callback callback;
|
||||
private final Allocator allocator;
|
||||
private final TrackGroupArray trackGroups;
|
||||
|
||||
private Callback callback;
|
||||
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
||||
private CompositeSequenceableLoader sequenceableLoader;
|
||||
private DashManifest manifest;
|
||||
@ -61,7 +61,7 @@ import java.util.List;
|
||||
public DashMediaPeriod(int id, DashManifest manifest, int index,
|
||||
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
|
||||
EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
|
||||
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) {
|
||||
LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
|
||||
this.id = id;
|
||||
this.manifest = manifest;
|
||||
this.index = index;
|
||||
@ -70,13 +70,11 @@ import java.util.List;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||
this.callback = callback;
|
||||
this.allocator = allocator;
|
||||
sampleStreams = newSampleStreamArray(0);
|
||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||
period = manifest.getPeriod(index);
|
||||
trackGroups = buildTrackGroups(period);
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
public void updateManifest(DashManifest manifest, int index) {
|
||||
@ -97,6 +95,12 @@ import java.util.List;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
this.callback = callback;
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
manifestLoaderErrorThrower.maybeThrowError();
|
||||
|
@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||
@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index,
|
||||
chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader,
|
||||
callback, allocator);
|
||||
allocator);
|
||||
periodsById.put(mediaPeriod.id, mediaPeriod);
|
||||
return mediaPeriod;
|
||||
}
|
||||
|
@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler
|
||||
} else if (MimeTypes.isVideo(containerMimeType)) {
|
||||
return MimeTypes.getVideoMediaMimeType(codecs);
|
||||
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
|
||||
// We currently only support EIA-608 through RawCC
|
||||
if (codecs != null && codecs.contains("eia608")) {
|
||||
return MimeTypes.APPLICATION_EIA608;
|
||||
// We currently only support CEA-608 through RawCC
|
||||
if (codecs != null
|
||||
&& (codecs.contains("eia608") || codecs.contains("cea608"))) {
|
||||
return MimeTypes.APPLICATION_CEA608;
|
||||
}
|
||||
return null;
|
||||
} else if (mimeTypeIsRawText(containerMimeType)) {
|
||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||
import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory;
|
||||
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
|
||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||
@ -355,20 +356,22 @@ import java.util.Locale;
|
||||
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
|
||||
startTimeUs);
|
||||
// This flag ensures the change of pid between streams does not affect the sample queues.
|
||||
int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE;
|
||||
@DefaultStreamReaderFactory.WorkaroundFlags
|
||||
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
|
||||
String codecs = variants[newVariantIndex].format.codecs;
|
||||
if (!TextUtils.isEmpty(codecs)) {
|
||||
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
|
||||
// exist. If we know from the codec attribute that they don't exist, then we can explicitly
|
||||
// ignore them even if they're declared.
|
||||
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
|
||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM;
|
||||
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_AAC_STREAM;
|
||||
}
|
||||
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
|
||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
|
||||
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
|
||||
}
|
||||
}
|
||||
extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
|
||||
extractor = new TsExtractor(timestampAdjuster,
|
||||
new DefaultStreamReaderFactory(workaroundFlags));
|
||||
} else {
|
||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||
extractor = previous.extractor;
|
||||
|
@ -50,11 +50,11 @@ import java.util.List;
|
||||
/* package */ final class HlsMediaPeriod implements MediaPeriod,
|
||||
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback {
|
||||
|
||||
private final Uri manifestUri;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final int minLoadableRetryCount;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final MediaSource.Listener sourceListener;
|
||||
private final Callback callback;
|
||||
private final Allocator allocator;
|
||||
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
|
||||
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
||||
@ -62,7 +62,9 @@ import java.util.List;
|
||||
private final Handler continueLoadingHandler;
|
||||
private final Loader manifestFetcher;
|
||||
private final long preparePositionUs;
|
||||
private final Runnable continueLoadingRunnable;
|
||||
|
||||
private Callback callback;
|
||||
private int pendingPrepareCount;
|
||||
private HlsPlaylist playlist;
|
||||
private boolean seenFirstTrackSelection;
|
||||
@ -72,17 +74,16 @@ import java.util.List;
|
||||
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
||||
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
||||
private CompositeSequenceableLoader sequenceableLoader;
|
||||
private Runnable continueLoadingRunnable;
|
||||
|
||||
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
|
||||
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
||||
MediaSource.Listener sourceListener, final Callback callback, Allocator allocator,
|
||||
MediaSource.Listener sourceListener, Allocator allocator,
|
||||
long positionUs) {
|
||||
this.manifestUri = manifestUri;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.sourceListener = sourceListener;
|
||||
this.callback = callback;
|
||||
this.allocator = allocator;
|
||||
streamWrapperIndices = new IdentityHashMap<>();
|
||||
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
||||
@ -96,11 +97,6 @@ import java.util.List;
|
||||
callback.onContinueLoadingRequested(HlsMediaPeriod.this);
|
||||
}
|
||||
};
|
||||
|
||||
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
|
||||
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
@ -111,6 +107,15 @@ import java.util.List;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
this.callback = callback;
|
||||
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
|
||||
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
|
||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
if (sampleStreamWrappers == null) {
|
||||
@ -239,13 +244,7 @@ import java.util.List;
|
||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||
loadDurationMs, loadable.bytesLoaded());
|
||||
playlist = loadable.getResult();
|
||||
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers();
|
||||
sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()];
|
||||
sampleStreamWrapperList.toArray(sampleStreamWrappers);
|
||||
pendingPrepareCount = sampleStreamWrappers.length;
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||
sampleStreamWrapper.prepare();
|
||||
}
|
||||
buildAndPrepareSampleStreamWrappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -313,16 +312,16 @@ import java.util.List;
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers() {
|
||||
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
|
||||
private void buildAndPrepareSampleStreamWrappers() {
|
||||
String baseUri = playlist.baseUri;
|
||||
|
||||
if (playlist instanceof HlsMediaPlaylist) {
|
||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
|
||||
HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
|
||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
||||
null, null));
|
||||
return sampleStreamWrappers;
|
||||
sampleStreamWrappers = new HlsSampleStreamWrapper[] {
|
||||
buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)};
|
||||
pendingPrepareCount = 1;
|
||||
sampleStreamWrappers[0].prepare();
|
||||
return;
|
||||
}
|
||||
|
||||
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
|
||||
@ -351,32 +350,37 @@ import java.util.List;
|
||||
} else {
|
||||
// Leave the enabled variants unchanged. They're likely either all video or all audio.
|
||||
}
|
||||
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios;
|
||||
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles;
|
||||
sampleStreamWrappers = new HlsSampleStreamWrapper[(selectedVariants.isEmpty() ? 0 : 1)
|
||||
+ audioVariants.size() + subtitleVariants.size()];
|
||||
int currentWrapperIndex = 0;
|
||||
pendingPrepareCount = sampleStreamWrappers.length;
|
||||
if (!selectedVariants.isEmpty()) {
|
||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()];
|
||||
selectedVariants.toArray(variants);
|
||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
||||
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat));
|
||||
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT,
|
||||
baseUri, variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat);
|
||||
sampleStreamWrapper.prepare();
|
||||
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||
}
|
||||
|
||||
// Build the audio stream wrapper if applicable.
|
||||
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios;
|
||||
if (!audioVariants.isEmpty()) {
|
||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[audioVariants.size()];
|
||||
audioVariants.toArray(variants);
|
||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
|
||||
null));
|
||||
// Build audio stream wrappers.
|
||||
for (int i = 0; i < audioVariants.size(); i++) {
|
||||
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO,
|
||||
baseUri, new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null);
|
||||
sampleStreamWrapper.prepare();
|
||||
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||
}
|
||||
|
||||
// Build the text stream wrapper if applicable.
|
||||
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles;
|
||||
if (!subtitleVariants.isEmpty()) {
|
||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[subtitleVariants.size()];
|
||||
subtitleVariants.toArray(variants);
|
||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
|
||||
null));
|
||||
// Build subtitle stream wrappers.
|
||||
for (int i = 0; i < subtitleVariants.size(); i++) {
|
||||
HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i);
|
||||
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT,
|
||||
baseUri, new HlsMasterPlaylist.HlsUrl[] {url}, null, null);
|
||||
sampleStreamWrapper.prepareSingleTrack(url.format);
|
||||
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||
}
|
||||
|
||||
return sampleStreamWrappers;
|
||||
}
|
||||
|
||||
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
|
||||
|
@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
Assertions.checkArgument(index == 0);
|
||||
return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount,
|
||||
eventDispatcher, sourceListener, callback, allocator, positionUs);
|
||||
eventDispatcher, sourceListener, allocator, positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.hls;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
@ -80,13 +82,15 @@ import java.util.LinkedList;
|
||||
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
|
||||
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
||||
private final LinkedList<HlsMediaChunk> mediaChunks;
|
||||
private final Runnable maybeFinishPrepareRunnable;
|
||||
private final Handler handler;
|
||||
|
||||
private volatile boolean sampleQueuesBuilt;
|
||||
|
||||
private boolean sampleQueuesBuilt;
|
||||
private boolean prepared;
|
||||
private int enabledTrackCount;
|
||||
private Format downstreamTrackFormat;
|
||||
private int upstreamChunkUid;
|
||||
private boolean released;
|
||||
|
||||
// Tracks are complicated in HLS. See documentation of buildTracks for details.
|
||||
// Indexed by track (as exposed by this source).
|
||||
@ -129,6 +133,13 @@ import java.util.LinkedList;
|
||||
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
|
||||
sampleQueues = new SparseArray<>();
|
||||
mediaChunks = new LinkedList<>();
|
||||
maybeFinishPrepareRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
maybeFinishPrepare();
|
||||
}
|
||||
};
|
||||
handler = new Handler();
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingResetPositionUs = positionUs;
|
||||
}
|
||||
@ -137,6 +148,15 @@ import java.util.LinkedList;
|
||||
continueLoading(lastSeekPositionUs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a sample stream wrapper for which the master playlist provides enough information to
|
||||
* prepare.
|
||||
*/
|
||||
public void prepareSingleTrack(Format format) {
|
||||
track(0).format(format);
|
||||
endTracks();
|
||||
}
|
||||
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
maybeThrowError();
|
||||
}
|
||||
@ -245,6 +265,8 @@ import java.util.LinkedList;
|
||||
sampleQueues.valueAt(i).disable();
|
||||
}
|
||||
loader.release();
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
released = true;
|
||||
}
|
||||
|
||||
public long getLargestQueuedTimestampUs() {
|
||||
@ -454,7 +476,7 @@ import java.util.LinkedList;
|
||||
@Override
|
||||
public void endTracks() {
|
||||
sampleQueuesBuilt = true;
|
||||
maybeFinishPrepare();
|
||||
handler.post(maybeFinishPrepareRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -462,17 +484,17 @@ import java.util.LinkedList;
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// UpstreamFormatChangedListener implementation.
|
||||
// UpstreamFormatChangedListener implementation. Called by the loading thread.
|
||||
|
||||
@Override
|
||||
public void onUpstreamFormatChanged(Format format) {
|
||||
maybeFinishPrepare();
|
||||
handler.post(maybeFinishPrepareRunnable);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void maybeFinishPrepare() {
|
||||
if (prepared || !sampleQueuesBuilt) {
|
||||
if (released || prepared || !sampleQueuesBuilt) {
|
||||
return;
|
||||
}
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
@ -558,7 +580,7 @@ import java.util.LinkedList;
|
||||
if (i == primaryExtractorTrackIndex) {
|
||||
Format[] formats = new Format[chunkSourceTrackCount];
|
||||
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
||||
formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
|
||||
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
|
||||
}
|
||||
trackGroups[i] = new TrackGroup(formats);
|
||||
primaryTrackGroupIndex = i;
|
||||
@ -567,11 +589,11 @@ import java.util.LinkedList;
|
||||
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
|
||||
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
|
||||
trackFormat = muxedAudioFormat;
|
||||
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
|
||||
} else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) {
|
||||
trackFormat = muxedCaptionFormat;
|
||||
}
|
||||
}
|
||||
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
|
||||
trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
|
||||
}
|
||||
}
|
||||
this.trackGroups = new TrackGroupArray(trackGroups);
|
||||
@ -590,18 +612,25 @@ import java.util.LinkedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a sample format corresponding to a given container format, by combining it with sample
|
||||
* level information obtained from a second sample format.
|
||||
* Derives a track format corresponding to a given container format, by combining it with sample
|
||||
* level information obtained from the samples.
|
||||
*
|
||||
* @param containerFormat The container format for which the sample format should be derived.
|
||||
* @param containerFormat The container format for which the track format should be derived.
|
||||
* @param sampleFormat A sample format from which to obtain sample level information.
|
||||
* @return The derived sample format.
|
||||
* @return The derived track format.
|
||||
*/
|
||||
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
|
||||
private static Format deriveFormat(Format containerFormat, Format sampleFormat) {
|
||||
if (containerFormat == null) {
|
||||
return sampleFormat;
|
||||
}
|
||||
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate,
|
||||
String codecs = null;
|
||||
int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType);
|
||||
if (sampleTrackType == C.TRACK_TYPE_AUDIO) {
|
||||
codecs = getAudioCodecs(containerFormat.codecs);
|
||||
} else if (sampleTrackType == C.TRACK_TYPE_VIDEO) {
|
||||
codecs = getVideoCodecs(containerFormat.codecs);
|
||||
}
|
||||
return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate,
|
||||
containerFormat.width, containerFormat.height, containerFormat.selectionFlags,
|
||||
containerFormat.language);
|
||||
}
|
||||
@ -614,4 +643,29 @@ import java.util.LinkedList;
|
||||
return pendingResetPositionUs != C.TIME_UNSET;
|
||||
}
|
||||
|
||||
private static String getAudioCodecs(String codecs) {
|
||||
return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
private static String getVideoCodecs(String codecs) {
|
||||
return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
private static String getCodecsOfType(String codecs, int trackType) {
|
||||
if (TextUtils.isEmpty(codecs)) {
|
||||
return null;
|
||||
}
|
||||
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String codec : codecArray) {
|
||||
if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append(codec);
|
||||
}
|
||||
}
|
||||
return builder.length() > 0 ? builder.toString() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,18 +15,29 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.hls.playlist;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Represents an HLS playlist.
|
||||
*/
|
||||
public abstract class HlsPlaylist {
|
||||
|
||||
/**
|
||||
* The type of playlist.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TYPE_MASTER, TYPE_MEDIA})
|
||||
public @interface Type {}
|
||||
public static final int TYPE_MASTER = 0;
|
||||
public static final int TYPE_MEDIA = 1;
|
||||
|
||||
public final String baseUri;
|
||||
@Type
|
||||
public final int type;
|
||||
|
||||
protected HlsPlaylist(String baseUri, int type) {
|
||||
protected HlsPlaylist(String baseUri, @Type int type) {
|
||||
this.baseUri = baseUri;
|
||||
this.type = type;
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
private static final String BOOLEAN_TRUE = "YES";
|
||||
private static final String BOOLEAN_FALSE = "NO";
|
||||
|
||||
|
||||
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
|
||||
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
|
||||
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
|
||||
@ -138,7 +137,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
while (iterator.hasNext()) {
|
||||
line = iterator.next();
|
||||
if (line.startsWith(TAG_MEDIA)) {
|
||||
int selectionFlags = parseSelectionFlags(line);
|
||||
@C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
|
||||
String uri = parseOptionalStringAttr(line, REGEX_URI);
|
||||
String name = parseStringAttr(line, REGEX_NAME);
|
||||
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
|
||||
@ -162,7 +161,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
case TYPE_CLOSED_CAPTIONS:
|
||||
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
|
||||
muxedCaptionFormat = Format.createTextContainerFormat(name,
|
||||
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE,
|
||||
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE,
|
||||
selectionFlags, language);
|
||||
}
|
||||
break;
|
||||
@ -200,11 +199,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
muxedCaptionFormat);
|
||||
}
|
||||
|
||||
@C.SelectionFlags
|
||||
private static int parseSelectionFlags(String line) {
|
||||
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0)
|
||||
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0)
|
||||
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT
|
||||
: 0);
|
||||
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0)
|
||||
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0)
|
||||
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0);
|
||||
}
|
||||
|
||||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
||||
|
@ -46,23 +46,22 @@ import java.util.ArrayList;
|
||||
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
||||
private final int minLoadableRetryCount;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final Callback callback;
|
||||
private final Allocator allocator;
|
||||
private final TrackGroupArray trackGroups;
|
||||
private final TrackEncryptionBox[] trackEncryptionBoxes;
|
||||
|
||||
private Callback callback;
|
||||
private SsManifest manifest;
|
||||
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
||||
private CompositeSequenceableLoader sequenceableLoader;
|
||||
|
||||
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
|
||||
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
||||
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) {
|
||||
LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
|
||||
this.chunkSourceFactory = chunkSourceFactory;
|
||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.callback = callback;
|
||||
this.allocator = allocator;
|
||||
|
||||
trackGroups = buildTrackGroups(manifest);
|
||||
@ -77,7 +76,6 @@ import java.util.ArrayList;
|
||||
this.manifest = manifest;
|
||||
sampleStreams = newSampleStreamArray(0);
|
||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
public void updateManifest(SsManifest manifest) {
|
||||
@ -94,6 +92,12 @@ import java.util.ArrayList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback) {
|
||||
this.callback = callback;
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
manifestLoaderErrorThrower.maybeThrowError();
|
||||
|
@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
||||
@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource,
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
||||
long positionUs) {
|
||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||
Assertions.checkArgument(index == 0);
|
||||
SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
|
||||
eventDispatcher, manifestLoader, callback, allocator);
|
||||
eventDispatcher, manifestLoader, allocator);
|
||||
mediaPeriods.add(period);
|
||||
return period;
|
||||
}
|
||||
|
@ -18,35 +18,41 @@ package com.google.android.exoplayer2.text;
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
import android.view.accessibility.CaptioningManager.CaptionStyle;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A compatibility wrapper for {@link CaptionStyle}.
|
||||
*/
|
||||
public final class CaptionStyleCompat {
|
||||
|
||||
/**
|
||||
* The type of edge, which may be none.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED,
|
||||
EDGE_TYPE_DEPRESSED})
|
||||
public @interface EdgeType {}
|
||||
/**
|
||||
* Edge type value specifying no character edges.
|
||||
*/
|
||||
public static final int EDGE_TYPE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Edge type value specifying uniformly outlined character edges.
|
||||
*/
|
||||
public static final int EDGE_TYPE_OUTLINE = 1;
|
||||
|
||||
/**
|
||||
* Edge type value specifying drop-shadowed character edges.
|
||||
*/
|
||||
public static final int EDGE_TYPE_DROP_SHADOW = 2;
|
||||
|
||||
/**
|
||||
* Edge type value specifying raised bevel character edges.
|
||||
*/
|
||||
public static final int EDGE_TYPE_RAISED = 3;
|
||||
|
||||
/**
|
||||
* Edge type value specifying depressed bevel character edges.
|
||||
*/
|
||||
@ -88,6 +94,7 @@ public final class CaptionStyleCompat {
|
||||
* <li>{@link #EDGE_TYPE_DEPRESSED}
|
||||
* </ul>
|
||||
*/
|
||||
@EdgeType
|
||||
public final int edgeType;
|
||||
|
||||
/**
|
||||
@ -126,8 +133,8 @@ public final class CaptionStyleCompat {
|
||||
* @param edgeColor See {@link #edgeColor}.
|
||||
* @param typeface See {@link #typeface}.
|
||||
*/
|
||||
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType,
|
||||
int edgeColor, Typeface typeface) {
|
||||
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor,
|
||||
@EdgeType int edgeType, int edgeColor, Typeface typeface) {
|
||||
this.foregroundColor = foregroundColor;
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.windowColor = windowColor;
|
||||
@ -137,6 +144,7 @@ public final class CaptionStyleCompat {
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
@SuppressWarnings("ResourceType")
|
||||
private static CaptionStyleCompat createFromCaptionStyleV19(
|
||||
CaptioningManager.CaptionStyle captionStyle) {
|
||||
return new CaptionStyleCompat(
|
||||
@ -145,6 +153,7 @@ public final class CaptionStyleCompat {
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
@SuppressWarnings("ResourceType")
|
||||
private static CaptionStyleCompat createFromCaptionStyleV21(
|
||||
CaptioningManager.CaptionStyle captionStyle) {
|
||||
return new CaptionStyleCompat(
|
||||
|
@ -15,7 +15,10 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.text;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.text.Layout.Alignment;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Contains information about a specific cue, including textual content and formatting data.
|
||||
@ -26,6 +29,13 @@ public class Cue {
|
||||
* An unset position or width.
|
||||
*/
|
||||
public static final float DIMEN_UNSET = Float.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* The type of anchor, which may be unset.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
|
||||
public @interface AnchorType {}
|
||||
/**
|
||||
* An unset anchor or line type value.
|
||||
*/
|
||||
@ -44,6 +54,13 @@ public class Cue {
|
||||
* box.
|
||||
*/
|
||||
public static final int ANCHOR_TYPE_END = 2;
|
||||
|
||||
/**
|
||||
* The type of line, which may be unset.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
|
||||
public @interface LineType {}
|
||||
/**
|
||||
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
||||
*/
|
||||
@ -83,6 +100,7 @@ public class Cue {
|
||||
* -1). For horizontal text the size of the first line of the cue is its height, and the start
|
||||
* and end of the viewport are the top and bottom respectively.
|
||||
*/
|
||||
@LineType
|
||||
public final int lineType;
|
||||
/**
|
||||
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
|
||||
@ -92,6 +110,7 @@ public class Cue {
|
||||
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
||||
* respectively.
|
||||
*/
|
||||
@AnchorType
|
||||
public final int lineAnchor;
|
||||
/**
|
||||
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
|
||||
@ -110,6 +129,7 @@ public class Cue {
|
||||
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
|
||||
* respectively.
|
||||
*/
|
||||
@AnchorType
|
||||
public final int positionAnchor;
|
||||
/**
|
||||
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
||||
@ -137,8 +157,8 @@ public class Cue {
|
||||
* @param positionAnchor See {@link #positionAnchor}.
|
||||
* @param size See {@link #size}.
|
||||
*/
|
||||
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor,
|
||||
float position, int positionAnchor, float size) {
|
||||
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
|
||||
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
|
||||
this.text = text;
|
||||
this.textAlignment = textAlignment;
|
||||
this.line = line;
|
||||
|
@ -16,7 +16,7 @@
|
||||
package com.google.android.exoplayer2.text;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.text.eia608.Eia608Decoder;
|
||||
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
|
||||
import com.google.android.exoplayer2.text.subrip.SubripDecoder;
|
||||
import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
|
||||
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
|
||||
@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory {
|
||||
* <li>TTML ({@link TtmlDecoder})</li>
|
||||
* <li>SubRip ({@link SubripDecoder})</li>
|
||||
* <li>TX3G ({@link Tx3gDecoder})</li>
|
||||
* <li>Eia608 ({@link Eia608Decoder})</li>
|
||||
* <li>Cea608 ({@link Cea608Decoder})</li>
|
||||
* </ul>
|
||||
*/
|
||||
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
|
||||
@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory {
|
||||
return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder");
|
||||
case MimeTypes.APPLICATION_TX3G:
|
||||
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
|
||||
case MimeTypes.APPLICATION_EIA608:
|
||||
return Class.forName("com.google.android.exoplayer2.text.eia608.Eia608Decoder");
|
||||
case MimeTypes.APPLICATION_CEA608:
|
||||
return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -13,26 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.text.eia608;
|
||||
package com.google.android.exoplayer2.text.cea;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.Subtitle;
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoder;
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.util.LinkedList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A {@link SubtitleDecoder} for EIA-608 (also known as "line 21 captions" and "CEA-608").
|
||||
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
|
||||
*/
|
||||
public final class Eia608Decoder implements SubtitleDecoder {
|
||||
public final class Cea608Decoder extends CeaDecoder {
|
||||
|
||||
private static final int NUM_INPUT_BUFFERS = 10;
|
||||
private static final int NUM_OUTPUT_BUFFERS = 2;
|
||||
private static final int NTSC_CC_FIELD_1 = 0x00;
|
||||
private static final int CC_VALID_FLAG = 0x04;
|
||||
|
||||
private static final int PAYLOAD_TYPE_CC = 4;
|
||||
private static final int COUNTRY_CODE = 0xB5;
|
||||
@ -159,18 +155,10 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
|
||||
};
|
||||
|
||||
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
|
||||
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
||||
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
||||
|
||||
private final ParsableByteArray ccData;
|
||||
|
||||
private final StringBuilder captionStringBuilder;
|
||||
|
||||
private long playbackPositionUs;
|
||||
|
||||
private SubtitleInputBuffer dequeuedInputBuffer;
|
||||
|
||||
private int captionMode;
|
||||
private int captionRowCount;
|
||||
private String captionString;
|
||||
@ -181,17 +169,7 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
private byte repeatableControlCc1;
|
||||
private byte repeatableControlCc2;
|
||||
|
||||
public Eia608Decoder() {
|
||||
availableInputBuffers = new LinkedList<>();
|
||||
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
||||
availableInputBuffers.add(new SubtitleInputBuffer());
|
||||
}
|
||||
availableOutputBuffers = new LinkedList<>();
|
||||
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
||||
availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this));
|
||||
}
|
||||
queuedInputBuffers = new TreeSet<>();
|
||||
|
||||
public Cea608Decoder() {
|
||||
ccData = new ParsableByteArray();
|
||||
|
||||
captionStringBuilder = new StringBuilder();
|
||||
@ -202,101 +180,20 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Eia608Decoder";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPositionUs(long positionUs) {
|
||||
playbackPositionUs = positionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
|
||||
Assertions.checkState(dequeuedInputBuffer == null);
|
||||
if (availableInputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
dequeuedInputBuffer = availableInputBuffers.pollFirst();
|
||||
return dequeuedInputBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
|
||||
Assertions.checkArgument(inputBuffer != null);
|
||||
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
|
||||
queuedInputBuffers.add(inputBuffer);
|
||||
dequeuedInputBuffer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
|
||||
if (availableOutputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate through all available input buffers whose timestamps are less than or equal
|
||||
// to the current playback position; processing input buffers for future content should
|
||||
// be deferred until they would be applicable
|
||||
while (!queuedInputBuffers.isEmpty()
|
||||
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
|
||||
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
|
||||
|
||||
// If the input buffer indicates we've reached the end of the stream, we can
|
||||
// return immediately with an output buffer propagating that
|
||||
if (inputBuffer.isEndOfStream()) {
|
||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
releaseInputBuffer(inputBuffer);
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
decode(inputBuffer);
|
||||
|
||||
// check if we have any caption updates to report
|
||||
if (!TextUtils.equals(captionString, lastCaptionString)) {
|
||||
lastCaptionString = captionString;
|
||||
if (!inputBuffer.isDecodeOnly()) {
|
||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||
outputBuffer.setContent(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0);
|
||||
releaseInputBuffer(inputBuffer);
|
||||
return outputBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
releaseInputBuffer(inputBuffer);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
|
||||
inputBuffer.clear();
|
||||
availableInputBuffers.add(inputBuffer);
|
||||
}
|
||||
|
||||
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
|
||||
outputBuffer.clear();
|
||||
availableOutputBuffers.add(outputBuffer);
|
||||
return "Cea608Decoder";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
super.flush();
|
||||
setCaptionMode(CC_MODE_UNKNOWN);
|
||||
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||
playbackPositionUs = 0;
|
||||
captionStringBuilder.setLength(0);
|
||||
captionString = null;
|
||||
lastCaptionString = null;
|
||||
repeatableControlSet = false;
|
||||
repeatableControlCc1 = 0;
|
||||
repeatableControlCc2 = 0;
|
||||
while (!queuedInputBuffers.isEmpty()) {
|
||||
releaseInputBuffer(queuedInputBuffers.pollFirst());
|
||||
}
|
||||
if (dequeuedInputBuffer != null) {
|
||||
releaseInputBuffer(dequeuedInputBuffer);
|
||||
dequeuedInputBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -304,14 +201,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
private void decode(SubtitleInputBuffer inputBuffer) {
|
||||
@Override
|
||||
protected boolean isNewSubtitleDataAvailable() {
|
||||
return !TextUtils.equals(captionString, lastCaptionString);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Subtitle createSubtitle() {
|
||||
lastCaptionString = captionString;
|
||||
return new CeaSubtitle(new Cue(captionString));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(SubtitleInputBuffer inputBuffer) {
|
||||
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
|
||||
boolean captionDataProcessed = false;
|
||||
boolean isRepeatableControl = false;
|
||||
while (ccData.bytesLeft() > 0) {
|
||||
byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07);
|
||||
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||
|
||||
// Only examine valid NTSC_CC_FIELD_1 packets
|
||||
if (ccTypeAndValid != (CC_VALID_FLAG | NTSC_CC_FIELD_1)) {
|
||||
// TODO: Add support for NTSC_CC_FIELD_2 packets
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore empty captions.
|
||||
if (ccData1 == 0 && ccData2 == 0) {
|
||||
continue;
|
||||
@ -320,26 +236,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
captionDataProcessed = true;
|
||||
|
||||
// Special North American character set.
|
||||
// ccData1 - P|0|0|1|C|0|0|1
|
||||
// ccData2 - P|0|1|1|X|X|X|X
|
||||
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
|
||||
// TODO: Make use of the channel bit
|
||||
captionStringBuilder.append(getSpecialChar(ccData2));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extended Spanish/Miscellaneous and French character set.
|
||||
// Extended Western European character set.
|
||||
// ccData1 - P|0|0|1|C|0|1|S
|
||||
// ccData2 - P|0|1|X|X|X|X|X
|
||||
if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) {
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
|
||||
continue;
|
||||
}
|
||||
if ((ccData2 & 0x60) == 0x20) {
|
||||
// Extended Spanish/Miscellaneous and French character set (S = 0).
|
||||
if (ccData1 == 0x12 || ccData1 == 0x1A) {
|
||||
// TODO: Make use of the channel bit
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extended Portuguese and German/Danish character set.
|
||||
// ccData2 - P|0|1|X|X|X|X|X
|
||||
if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) {
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
|
||||
continue;
|
||||
// Extended Portuguese and German/Danish character set (S = 1).
|
||||
if (ccData1 == 0x13 || ccData1 == 0x1B) {
|
||||
// TODO: Make use of the channel bit
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Control character.
|
||||
@ -367,15 +290,17 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
|
||||
private boolean handleCtrl(byte cc1, byte cc2) {
|
||||
boolean isRepeatableControl = isRepeatable(cc1);
|
||||
if (isRepeatableControl && repeatableControlSet
|
||||
&& repeatableControlCc1 == cc1
|
||||
&& repeatableControlCc2 == cc2) {
|
||||
repeatableControlSet = false;
|
||||
return true;
|
||||
} else if (isRepeatableControl) {
|
||||
repeatableControlSet = true;
|
||||
repeatableControlCc1 = cc1;
|
||||
repeatableControlCc2 = cc2;
|
||||
if (isRepeatableControl) {
|
||||
if (repeatableControlSet
|
||||
&& repeatableControlCc1 == cc1
|
||||
&& repeatableControlCc2 == cc2) {
|
||||
repeatableControlSet = false;
|
||||
return true;
|
||||
} else {
|
||||
repeatableControlSet = true;
|
||||
repeatableControlCc1 = cc1;
|
||||
repeatableControlCc2 = cc2;
|
||||
}
|
||||
}
|
||||
if (isMiscCode(cc1, cc2)) {
|
||||
handleMiscCode(cc2);
|
||||
@ -526,16 +451,16 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects an sei message to determine whether it contains EIA-608.
|
||||
* Inspects an sei message to determine whether it contains CEA-608.
|
||||
* <p>
|
||||
* The position of {@code payload} is left unchanged.
|
||||
*
|
||||
* @param payloadType The payload type of the message.
|
||||
* @param payloadLength The length of the payload.
|
||||
* @param payload A {@link ParsableByteArray} containing the payload.
|
||||
* @return Whether the sei message contains EIA-608.
|
||||
* @return Whether the sei message contains CEA-608.
|
||||
*/
|
||||
public static boolean isSeiMessageEia608(int payloadType, int payloadLength,
|
||||
public static boolean isSeiMessageCea608(int payloadType, int payloadLength,
|
||||
ParsableByteArray payload) {
|
||||
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
|
||||
return false;
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.text.cea;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Subtitle;
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoder;
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.util.LinkedList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Base class for subtitle parsers for CEA captions.
|
||||
*/
|
||||
/* package */ abstract class CeaDecoder implements SubtitleDecoder {
|
||||
|
||||
private static final int NUM_INPUT_BUFFERS = 10;
|
||||
private static final int NUM_OUTPUT_BUFFERS = 2;
|
||||
|
||||
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
|
||||
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
||||
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
||||
|
||||
private SubtitleInputBuffer dequeuedInputBuffer;
|
||||
private long playbackPositionUs;
|
||||
|
||||
public CeaDecoder() {
|
||||
availableInputBuffers = new LinkedList<>();
|
||||
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
||||
availableInputBuffers.add(new SubtitleInputBuffer());
|
||||
}
|
||||
availableOutputBuffers = new LinkedList<>();
|
||||
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
||||
availableOutputBuffers.add(new CeaOutputBuffer(this));
|
||||
}
|
||||
queuedInputBuffers = new TreeSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String getName();
|
||||
|
||||
@Override
|
||||
public void setPositionUs(long positionUs) {
|
||||
playbackPositionUs = positionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
|
||||
Assertions.checkState(dequeuedInputBuffer == null);
|
||||
if (availableInputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
dequeuedInputBuffer = availableInputBuffers.pollFirst();
|
||||
return dequeuedInputBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
|
||||
Assertions.checkArgument(inputBuffer != null);
|
||||
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
|
||||
queuedInputBuffers.add(inputBuffer);
|
||||
dequeuedInputBuffer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
|
||||
if (availableOutputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate through all available input buffers whose timestamps are less than or equal
|
||||
// to the current playback position; processing input buffers for future content should
|
||||
// be deferred until they would be applicable
|
||||
while (!queuedInputBuffers.isEmpty()
|
||||
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
|
||||
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
|
||||
|
||||
// If the input buffer indicates we've reached the end of the stream, we can
|
||||
// return immediately with an output buffer propagating that
|
||||
if (inputBuffer.isEndOfStream()) {
|
||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
releaseInputBuffer(inputBuffer);
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
decode(inputBuffer);
|
||||
|
||||
// check if we have any caption updates to report
|
||||
if (isNewSubtitleDataAvailable()) {
|
||||
// Even if the subtitle is decode-only; we need to generate it to consume the data so it
|
||||
// isn't accidentally prepended to the next subtitle
|
||||
Subtitle subtitle = createSubtitle();
|
||||
if (!inputBuffer.isDecodeOnly()) {
|
||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||
outputBuffer.setContent(inputBuffer.timeUs, subtitle, 0);
|
||||
releaseInputBuffer(inputBuffer);
|
||||
return outputBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
releaseInputBuffer(inputBuffer);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
|
||||
inputBuffer.clear();
|
||||
availableInputBuffers.add(inputBuffer);
|
||||
}
|
||||
|
||||
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
|
||||
outputBuffer.clear();
|
||||
availableOutputBuffers.add(outputBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
playbackPositionUs = 0;
|
||||
while (!queuedInputBuffers.isEmpty()) {
|
||||
releaseInputBuffer(queuedInputBuffers.pollFirst());
|
||||
}
|
||||
if (dequeuedInputBuffer != null) {
|
||||
releaseInputBuffer(dequeuedInputBuffer);
|
||||
dequeuedInputBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is data available to create a new {@link Subtitle}.
|
||||
*/
|
||||
protected abstract boolean isNewSubtitleDataAvailable();
|
||||
|
||||
/**
|
||||
* Creates a {@link Subtitle} from the available data.
|
||||
*/
|
||||
protected abstract Subtitle createSubtitle();
|
||||
|
||||
/**
|
||||
* Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()}
|
||||
* when sufficient data has been processed.
|
||||
*/
|
||||
protected abstract void decode(SubtitleInputBuffer inputBuffer);
|
||||
|
||||
}
|
@ -13,22 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.text.eia608;
|
||||
package com.google.android.exoplayer2.text.cea;
|
||||
|
||||
import com.google.android.exoplayer2.text.Subtitle;
|
||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||
|
||||
/**
|
||||
* A {@link Subtitle} output from an {@link Eia608Decoder}.
|
||||
* A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s.
|
||||
*/
|
||||
/* package */ final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer {
|
||||
public final class CeaOutputBuffer extends SubtitleOutputBuffer {
|
||||
|
||||
private Eia608Decoder owner;
|
||||
private final CeaDecoder owner;
|
||||
|
||||
/**
|
||||
* @param owner The decoder that owns this buffer.
|
||||
*/
|
||||
public Eia608SubtitleOutputBuffer(Eia608Decoder owner) {
|
||||
public CeaOutputBuffer(CeaDecoder owner) {
|
||||
super();
|
||||
this.owner = owner;
|
||||
}
|
@ -13,26 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.text.eia608;
|
||||
package com.google.android.exoplayer2.text.cea;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.Subtitle;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A representation of an EIA-608 subtitle.
|
||||
* A representation of a CEA subtitle.
|
||||
*/
|
||||
/* package */ final class Eia608Subtitle implements Subtitle {
|
||||
/* package */ final class CeaSubtitle implements Subtitle {
|
||||
|
||||
private final String text;
|
||||
private final List<Cue> cues;
|
||||
|
||||
/**
|
||||
* @param text The subtitle text.
|
||||
* @param cue The subtitle cue.
|
||||
*/
|
||||
public Eia608Subtitle(String text) {
|
||||
this.text = text;
|
||||
public CeaSubtitle(Cue cue) {
|
||||
if (cue == null) {
|
||||
cues = Collections.emptyList();
|
||||
} else {
|
||||
cues = Collections.singletonList(cue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -52,11 +55,8 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public List<Cue> getCues(long timeUs) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Collections.singletonList(new Cue(text));
|
||||
}
|
||||
return cues;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
|
||||
public final float position;
|
||||
public final float line;
|
||||
@Cue.LineType
|
||||
public final int lineType;
|
||||
public final float width;
|
||||
|
||||
@ -31,7 +32,7 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
}
|
||||
|
||||
public TtmlRegion(float position, float line, int lineType, float width) {
|
||||
public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) {
|
||||
this.position = position;
|
||||
this.line = line;
|
||||
this.lineType = lineType;
|
||||
|
@ -16,8 +16,11 @@
|
||||
package com.google.android.exoplayer2.text.ttml;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.text.Layout;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Style object of a <code>TtmlNode</code>
|
||||
@ -26,15 +29,25 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
public static final int UNSPECIFIED = -1;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
|
||||
STYLE_BOLD_ITALIC})
|
||||
public @interface StyleFlags {}
|
||||
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
||||
public static final int STYLE_BOLD = Typeface.BOLD;
|
||||
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
||||
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
|
||||
public @interface FontSizeUnit {}
|
||||
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
||||
public static final int FONT_SIZE_UNIT_EM = 2;
|
||||
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({UNSPECIFIED, OFF, ON})
|
||||
private @interface OptionalBoolean {}
|
||||
private static final int OFF = 0;
|
||||
private static final int ON = 1;
|
||||
|
||||
@ -43,10 +56,15 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
private boolean hasFontColor;
|
||||
private int backgroundColor;
|
||||
private boolean hasBackgroundColor;
|
||||
@OptionalBoolean
|
||||
private int linethrough;
|
||||
@OptionalBoolean
|
||||
private int underline;
|
||||
@OptionalBoolean
|
||||
private int bold;
|
||||
@OptionalBoolean
|
||||
private int italic;
|
||||
@FontSizeUnit
|
||||
private int fontSizeUnit;
|
||||
private float fontSize;
|
||||
private String id;
|
||||
@ -67,12 +85,13 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||
* or {@link #STYLE_BOLD_ITALIC}.
|
||||
*/
|
||||
@StyleFlags
|
||||
public int getStyle() {
|
||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
||||
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
||||
return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
|
||||
| (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
|
||||
}
|
||||
|
||||
public boolean isLinethrough() {
|
||||
@ -95,6 +114,18 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TtmlStyle setBold(boolean bold) {
|
||||
Assertions.checkState(inheritableStyle == null);
|
||||
this.bold = bold ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TtmlStyle setItalic(boolean italic) {
|
||||
Assertions.checkState(inheritableStyle == null);
|
||||
this.italic = italic ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
}
|
||||
@ -140,18 +171,6 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
return hasBackgroundColor;
|
||||
}
|
||||
|
||||
public TtmlStyle setBold(boolean isBold) {
|
||||
Assertions.checkState(inheritableStyle == null);
|
||||
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TtmlStyle setItalic(boolean isItalic) {
|
||||
Assertions.checkState(inheritableStyle == null);
|
||||
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
|
||||
* are not inheritable are not inherited as well as properties which are already set locally
|
||||
@ -236,6 +255,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@FontSizeUnit
|
||||
public int getFontSizeUnit() {
|
||||
return fontSizeUnit;
|
||||
}
|
||||
|
@ -16,8 +16,11 @@
|
||||
package com.google.android.exoplayer2.text.webvtt;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.text.Layout;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -32,15 +35,25 @@ import java.util.List;
|
||||
|
||||
public static final int UNSPECIFIED = -1;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
|
||||
STYLE_BOLD_ITALIC})
|
||||
public @interface StyleFlags {}
|
||||
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
||||
public static final int STYLE_BOLD = Typeface.BOLD;
|
||||
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
||||
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
|
||||
public @interface FontSizeUnit {}
|
||||
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
||||
public static final int FONT_SIZE_UNIT_EM = 2;
|
||||
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({UNSPECIFIED, OFF, ON})
|
||||
private @interface OptionalBoolean {}
|
||||
private static final int OFF = 0;
|
||||
private static final int ON = 1;
|
||||
|
||||
@ -56,10 +69,15 @@ import java.util.List;
|
||||
private boolean hasFontColor;
|
||||
private int backgroundColor;
|
||||
private boolean hasBackgroundColor;
|
||||
@OptionalBoolean
|
||||
private int linethrough;
|
||||
@OptionalBoolean
|
||||
private int underline;
|
||||
@OptionalBoolean
|
||||
private int bold;
|
||||
@OptionalBoolean
|
||||
private int italic;
|
||||
@FontSizeUnit
|
||||
private int fontSizeUnit;
|
||||
private float fontSize;
|
||||
private Layout.Alignment textAlign;
|
||||
@ -144,12 +162,13 @@ import java.util.List;
|
||||
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||
* or {@link #STYLE_BOLD_ITALIC}.
|
||||
*/
|
||||
@StyleFlags
|
||||
public int getStyle() {
|
||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
||||
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
||||
return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
|
||||
| (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
|
||||
}
|
||||
|
||||
public boolean isLinethrough() {
|
||||
@ -169,6 +188,15 @@ import java.util.List;
|
||||
this.underline = underline ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
public WebvttCssStyle setBold(boolean bold) {
|
||||
this.bold = bold ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setItalic(boolean italic) {
|
||||
this.italic = italic ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
@ -213,16 +241,6 @@ import java.util.List;
|
||||
return hasBackgroundColor;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setBold(boolean isBold) {
|
||||
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setItalic(boolean isItalic) {
|
||||
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Layout.Alignment getTextAlign() {
|
||||
return textAlign;
|
||||
}
|
||||
@ -242,6 +260,7 @@ import java.util.List;
|
||||
return this;
|
||||
}
|
||||
|
||||
@FontSizeUnit
|
||||
public int getFontSizeUnit() {
|
||||
return fontSizeUnit;
|
||||
}
|
||||
|
@ -37,8 +37,9 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
}
|
||||
|
||||
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
||||
float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) {
|
||||
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
||||
float line, @Cue.LineType int lineType, @Cue.AnchorType int lineAnchor, float position,
|
||||
@Cue.AnchorType int positionAnchor, float width) {
|
||||
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
|
@ -18,22 +18,301 @@ package com.google.android.exoplayer2.trackselection;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A {@link MappingTrackSelector} that allows configuration of common parameters.
|
||||
* A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call
|
||||
* the methods of this class from the application thread. See {@link Parameters#Parameters()} for
|
||||
* default selection parameters.
|
||||
*/
|
||||
public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
/**
|
||||
* Holder for available configurations for the {@link DefaultTrackSelector}.
|
||||
*/
|
||||
public static final class Parameters {
|
||||
|
||||
// Audio.
|
||||
public final String preferredAudioLanguage;
|
||||
|
||||
// Text.
|
||||
public final String preferredTextLanguage;
|
||||
|
||||
// Video.
|
||||
public final boolean allowMixedMimeAdaptiveness;
|
||||
public final boolean allowNonSeamlessAdaptiveness;
|
||||
public final int maxVideoWidth;
|
||||
public final int maxVideoHeight;
|
||||
public final boolean exceedVideoConstraintsIfNecessary;
|
||||
public final int viewportWidth;
|
||||
public final int viewportHeight;
|
||||
public final boolean orientationMayChange;
|
||||
|
||||
/**
|
||||
* Constructor with default selection parameters:
|
||||
* <ul>
|
||||
* <li>No preferred audio language is set.</li>
|
||||
* <li>No preferred text language is set.</li>
|
||||
* <li>Adaptation between different mime types is not allowed.</li>
|
||||
* <li>Non seamless adaptation is allowed.</li>
|
||||
* <li>No max limit for video width/height.</li>
|
||||
* <li>Video constraints are ignored if no supported selection can be made otherwise.</li>
|
||||
* <li>No viewport width/height constraints are set.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Parameters() {
|
||||
this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE,
|
||||
Integer.MAX_VALUE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param preferredAudioLanguage The preferred language for audio, as well as for forced text
|
||||
* tracks as defined by RFC 5646. {@code null} to select the default track, or first track
|
||||
* if there's no default.
|
||||
* @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646.
|
||||
* {@code null} to select the default track, or first track if there's no default.
|
||||
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
||||
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
||||
* @param maxVideoWidth Maximum allowed video width.
|
||||
* @param maxVideoHeight Maximum allowed video height.
|
||||
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
||||
* can be made otherwise. False to force constraints anyway.
|
||||
* @param viewportWidth Viewport width in pixels.
|
||||
* @param viewportHeight Viewport height in pixels.
|
||||
* @param orientationMayChange Whether orientation may change during playback.
|
||||
*/
|
||||
public Parameters(String preferredAudioLanguage, String preferredTextLanguage,
|
||||
boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness,
|
||||
int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary,
|
||||
int viewportWidth, int viewportHeight, boolean orientationMayChange) {
|
||||
this.preferredAudioLanguage = preferredAudioLanguage;
|
||||
this.preferredTextLanguage = preferredTextLanguage;
|
||||
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
|
||||
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
|
||||
this.maxVideoWidth = maxVideoWidth;
|
||||
this.maxVideoHeight = maxVideoHeight;
|
||||
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
|
||||
this.viewportWidth = viewportWidth;
|
||||
this.viewportHeight = viewportHeight;
|
||||
this.orientationMayChange = orientationMayChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided preferred language for audio and
|
||||
* forced text tracks.
|
||||
*
|
||||
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||
* select the default track, or first track if there's no default.
|
||||
* @return A {@link Parameters} instance with the provided preferred language for audio and
|
||||
* forced text tracks.
|
||||
*/
|
||||
public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) {
|
||||
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
|
||||
if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight,
|
||||
exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided preferred language for text tracks.
|
||||
*
|
||||
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||
* select the default track, or no track if there's no default.
|
||||
* @return A {@link Parameters} instance with the provided preferred language for text tracks.
|
||||
*/
|
||||
public Parameters withPreferredTextLanguage(String preferredTextLanguage) {
|
||||
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
|
||||
if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
|
||||
*
|
||||
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
||||
* @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
|
||||
*/
|
||||
public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
|
||||
if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance.
|
||||
*
|
||||
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
||||
* @return A {@link Parameters} instance with the provided seamless adaptiveness allowance.
|
||||
*/
|
||||
public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
|
||||
if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided max video size.
|
||||
*
|
||||
* @param maxVideoWidth The max video width.
|
||||
* @param maxVideoHeight The max video width.
|
||||
* @return A {@link Parameters} instance with the provided max video size.
|
||||
*/
|
||||
public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
|
||||
if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code withMaxVideoSize(1279, 719)}.
|
||||
*
|
||||
* @return A {@link Parameters} instance with maximum standard definition as maximum video size.
|
||||
*/
|
||||
public Parameters withMaxVideoSizeSd() {
|
||||
return withMaxVideoSize(1279, 719);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
|
||||
*
|
||||
* @return A {@link Parameters} instance without video size constraints.
|
||||
*/
|
||||
public Parameters withoutVideoSizeConstraints() {
|
||||
return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided
|
||||
* {@code exceedVideoConstraintsIfNecessary} value.
|
||||
*
|
||||
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
||||
* can be made otherwise. False to force constraints anyway.
|
||||
* @return A {@link Parameters} instance with the provided
|
||||
* {@code exceedVideoConstraintsIfNecessary} value.
|
||||
*/
|
||||
public Parameters withExceedVideoConstraintsIfNecessary(
|
||||
boolean exceedVideoConstraintsIfNecessary) {
|
||||
if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance with the provided viewport size.
|
||||
*
|
||||
* @param viewportWidth Viewport width in pixels.
|
||||
* @param viewportHeight Viewport height in pixels.
|
||||
* @param orientationMayChange Whether orientation may change during playback.
|
||||
* @return A {@link Parameters} instance with the provided viewport size.
|
||||
*/
|
||||
public Parameters withViewportSize(int viewportWidth, int viewportHeight,
|
||||
boolean orientationMayChange) {
|
||||
if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight
|
||||
&& orientationMayChange == this.orientationMayChange) {
|
||||
return this;
|
||||
}
|
||||
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||
orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Parameters} instance where the viewport size is obtained from the provided
|
||||
* {@link Context}.
|
||||
*
|
||||
* @param context The context to obtain the viewport size from.
|
||||
* @param orientationMayChange Whether orientation may change during playback.
|
||||
* @return A {@link Parameters} instance where the viewport size is obtained from the provided
|
||||
* {@link Context}.
|
||||
*/
|
||||
public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) {
|
||||
// Assume the viewport is fullscreen.
|
||||
Point viewportSize = Util.getPhysicalDisplaySize(context);
|
||||
return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
|
||||
*
|
||||
* @return A {@link Parameters} instance without viewport size constraints.
|
||||
*/
|
||||
public Parameters withoutViewportSizeConstraints() {
|
||||
return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Parameters other = (Parameters) obj;
|
||||
return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness
|
||||
&& allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness
|
||||
&& maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight
|
||||
&& exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary
|
||||
&& orientationMayChange == other.orientationMayChange
|
||||
&& viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
|
||||
&& TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage)
|
||||
&& TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = preferredAudioLanguage.hashCode();
|
||||
result = 31 * result + preferredTextLanguage.hashCode();
|
||||
result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0);
|
||||
result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0);
|
||||
result = 31 * result + maxVideoWidth;
|
||||
result = 31 * result + maxVideoHeight;
|
||||
result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0);
|
||||
result = 31 * result + (orientationMayChange ? 1 : 0);
|
||||
result = 31 * result + viewportWidth;
|
||||
result = 31 * result + viewportHeight;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
|
||||
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
|
||||
@ -43,22 +322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
private static final int[] NO_TRACKS = new int[0];
|
||||
|
||||
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
||||
|
||||
// Audio.
|
||||
private String preferredAudioLanguage;
|
||||
|
||||
// Text.
|
||||
private String preferredTextLanguage;
|
||||
|
||||
// Video.
|
||||
private boolean allowMixedMimeAdaptiveness;
|
||||
private boolean allowNonSeamlessAdaptiveness;
|
||||
private int maxVideoWidth;
|
||||
private int maxVideoHeight;
|
||||
private boolean exceedVideoConstraintsIfNecessary;
|
||||
private boolean orientationMayChange;
|
||||
private int viewportWidth;
|
||||
private int viewportHeight;
|
||||
private final AtomicReference<Parameters> params;
|
||||
|
||||
/**
|
||||
* Constructs an instance that does not support adaptive video.
|
||||
@ -82,142 +346,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
|
||||
super(eventHandler);
|
||||
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
|
||||
allowNonSeamlessAdaptiveness = true;
|
||||
exceedVideoConstraintsIfNecessary = true;
|
||||
maxVideoWidth = Integer.MAX_VALUE;
|
||||
maxVideoHeight = Integer.MAX_VALUE;
|
||||
viewportWidth = Integer.MAX_VALUE;
|
||||
viewportHeight = Integer.MAX_VALUE;
|
||||
orientationMayChange = true;
|
||||
params = new AtomicReference<>(new Parameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preferred language for audio, as well as for forced text tracks.
|
||||
* Atomically sets the provided parameters for track selection.
|
||||
*
|
||||
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||
* select the default track, or first track if there's no default.
|
||||
* @param params The parameters for track selection.
|
||||
*/
|
||||
public void setPreferredLanguages(String preferredAudioLanguage) {
|
||||
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
|
||||
if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) {
|
||||
this.preferredAudioLanguage = preferredAudioLanguage;
|
||||
public void setParameters(Parameters params) {
|
||||
if (!this.params.get().equals(params)) {
|
||||
this.params.set(Assertions.checkNotNull(params));
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preferred language for text tracks.
|
||||
* Gets the current selection parameters.
|
||||
*
|
||||
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||
* select the default track, or no track if there's no default.
|
||||
* @return The current selection parameters.
|
||||
*/
|
||||
public void setPreferredTextLanguage(String preferredTextLanguage) {
|
||||
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
|
||||
if (!Util.areEqual(this.preferredTextLanguage, preferredTextLanguage)) {
|
||||
this.preferredTextLanguage = preferredTextLanguage;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to allow selections to contain mixed mime types.
|
||||
*
|
||||
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
||||
*/
|
||||
public void allowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
|
||||
if (this.allowMixedMimeAdaptiveness != allowMixedMimeAdaptiveness) {
|
||||
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether non-seamless adaptation is allowed.
|
||||
*
|
||||
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
||||
*/
|
||||
public void allowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
|
||||
if (this.allowNonSeamlessAdaptiveness != allowNonSeamlessAdaptiveness) {
|
||||
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum allowed size for video tracks.
|
||||
*
|
||||
* @param maxVideoWidth Maximum allowed width.
|
||||
* @param maxVideoHeight Maximum allowed height.
|
||||
*/
|
||||
public void setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
|
||||
if (this.maxVideoWidth != maxVideoWidth || this.maxVideoHeight != maxVideoHeight) {
|
||||
this.maxVideoWidth = maxVideoWidth;
|
||||
this.maxVideoHeight = maxVideoHeight;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code setMaxVideoSize(1279, 719)}.
|
||||
*/
|
||||
public void setMaxVideoSizeSd() {
|
||||
setMaxVideoSize(1279, 719);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
|
||||
*/
|
||||
public void clearMaxVideoSize() {
|
||||
setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether video constraints should be ignored when no selection can be made otherwise.
|
||||
*
|
||||
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
||||
* can be made otherwise. False to force constraints anyway.
|
||||
*/
|
||||
public void setExceedVideoConstraintsIfNecessary(boolean exceedVideoConstraintsIfNecessary) {
|
||||
if (this.exceedVideoConstraintsIfNecessary != exceedVideoConstraintsIfNecessary) {
|
||||
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target viewport size for selecting video tracks.
|
||||
*
|
||||
* @param viewportWidth Viewport width in pixels.
|
||||
* @param viewportHeight Viewport height in pixels.
|
||||
* @param orientationMayChange Whether orientation may change during playback.
|
||||
*/
|
||||
public void setViewportSize(int viewportWidth, int viewportHeight, boolean orientationMayChange) {
|
||||
if (this.viewportWidth != viewportWidth || this.viewportHeight != viewportHeight
|
||||
|| this.orientationMayChange != orientationMayChange) {
|
||||
this.viewportWidth = viewportWidth;
|
||||
this.viewportHeight = viewportHeight;
|
||||
this.orientationMayChange = orientationMayChange;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the viewport size from the provided {@link Context} and calls
|
||||
* {@link #setViewportSize(int, int, boolean)} with this information.
|
||||
*
|
||||
* @param context The context to obtain the viewport size from.
|
||||
* @param orientationMayChange Whether orientation may change during playback.
|
||||
*/
|
||||
public void setViewportSizeFromContext(Context context, boolean orientationMayChange) {
|
||||
Point viewportSize = Util.getPhysicalDisplaySize(context); // Assume the viewport is fullscreen.
|
||||
setViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
|
||||
*/
|
||||
public void clearViewportConstraints() {
|
||||
setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
|
||||
public Parameters getParameters() {
|
||||
return params.get();
|
||||
}
|
||||
|
||||
// MappingTrackSelector implementation.
|
||||
@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
throws ExoPlaybackException {
|
||||
// Make a track selection for each renderer.
|
||||
TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length];
|
||||
Parameters params = this.params.get();
|
||||
for (int i = 0; i < rendererCapabilities.length; i++) {
|
||||
switch (rendererCapabilities[i].getTrackType()) {
|
||||
case C.TRACK_TYPE_VIDEO:
|
||||
rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i],
|
||||
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight,
|
||||
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth,
|
||||
viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory,
|
||||
exceedVideoConstraintsIfNecessary);
|
||||
rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth,
|
||||
params.maxVideoHeight, params.allowNonSeamlessAdaptiveness,
|
||||
params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight,
|
||||
params.orientationMayChange, adaptiveVideoTrackSelectionFactory,
|
||||
params.exceedVideoConstraintsIfNecessary);
|
||||
break;
|
||||
case C.TRACK_TYPE_AUDIO:
|
||||
rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i],
|
||||
rendererFormatSupports[i], preferredAudioLanguage);
|
||||
rendererFormatSupports[i], params.preferredAudioLanguage);
|
||||
break;
|
||||
case C.TRACK_TYPE_TEXT:
|
||||
rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i],
|
||||
rendererFormatSupports[i], preferredTextLanguage, preferredAudioLanguage);
|
||||
rendererFormatSupports[i], params.preferredTextLanguage,
|
||||
params.preferredAudioLanguage);
|
||||
break;
|
||||
default:
|
||||
rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(),
|
||||
@ -442,7 +595,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
||||
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||
int trackScore;
|
||||
if (formatHasLanguage(format, preferredAudioLanguage)) {
|
||||
if (isDefault) {
|
||||
@ -480,8 +633,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
||||
boolean isForced = (format.selectionFlags & Format.SELECTION_FLAG_FORCED) != 0;
|
||||
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||
boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0;
|
||||
int trackScore;
|
||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||
if (isDefault) {
|
||||
@ -530,7 +683,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
||||
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||
int trackScore = isDefault ? 2 : 1;
|
||||
if (trackScore > selectedTrackScore) {
|
||||
selectedGroup = trackGroup;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user