Merge pull request #1879 from google/dev-v2

r2.0.1
This commit is contained in:
ojw28 2016-09-30 15:08:06 +01:00 committed by GitHub
commit f8a8302f7b
232 changed files with 3345 additions and 2000 deletions

View File

@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates.
#### Via jCenter #### #### Via jCenter ####
The easiest way to get started using ExoPlayer is by including the following in The easiest way to get started using ExoPlayer is to add it as a gradle
your project's `build.gradle` file: 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 ```gradle
compile 'com.google.android.exoplayer:exoplayer:rX.X.X' compile 'com.google.android.exoplayer:exoplayer:rX.X.X'

View File

@ -1,5 +1,14 @@
# Release notes # # 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 ### ### r2.0.0 ###
ExoPlayer 2.x is a major iteration of the library. It includes significant API ExoPlayer 2.x is a major iteration of the library. It includes significant API

View File

@ -39,14 +39,14 @@ android {
productFlavors { productFlavors {
demo demo
demo_ext demoExt
} }
} }
dependencies { dependencies {
compile project(':library') compile project(':library')
demo_extCompile project(path: ':extension-ffmpeg') demoExtCompile project(path: ':extension-ffmpeg')
demo_extCompile project(path: ':extension-flac') demoExtCompile project(path: ':extension-flac')
demo_extCompile project(path: ':extension-opus') demoExtCompile project(path: ':extension-opus')
demo_extCompile project(path: ':extension-vp9') demoExtCompile project(path: ':extension-vp9')
} }

View File

@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2000" android:versionCode="2001"
android:versionName="2.0.0"> android:versionName="2.0.1">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

View File

@ -16,9 +16,33 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Application; 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. * Placeholder application to facilitate overriding Application methods for debugging and testing.
*/ */
public class DemoApplication extends Application { 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);
}
} }

View File

@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; 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.upstream.DataSpec;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException; import java.io.IOException;
@ -54,7 +55,7 @@ import java.util.Locale;
/* package */ final class EventLogger implements ExoPlayer.EventListener, /* package */ final class EventLogger implements ExoPlayer.EventListener,
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener, AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, 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 String TAG = "EventLogger";
private static final int MAX_TIMELINE_ITEM_LINES = 3; private static final int MAX_TIMELINE_ITEM_LINES = 3;
@ -125,23 +126,24 @@ import java.util.Locale;
// MappingTrackSelector.EventListener // MappingTrackSelector.EventListener
@Override @Override
public void onTracksChanged(TrackInfo trackInfo) { public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
Log.d(TAG, "Tracks ["); Log.d(TAG, "Tracks [");
// Log tracks associated to renderers. // Log tracks associated to renderers.
for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) { MappedTrackInfo info = trackSelections.info;
TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex); for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) {
TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex); TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex);
TrackSelection trackSelection = trackSelections.get(rendererIndex);
if (trackGroups.length > 0) { if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:" + rendererIndex + " ["); Log.d(TAG, " Renderer:" + rendererIndex + " [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = trackGroups.get(groupIndex);
String adaptiveSupport = getAdaptiveSupportString( 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 + " ["); Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
String formatSupport = getFormatSupportString( String formatSupport = getFormatSupportString(
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
Log.d(TAG, " " + status + " Track:" + trackIndex + ", " Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
+ getFormatString(trackGroup.getFormat(trackIndex)) + getFormatString(trackGroup.getFormat(trackIndex))
+ ", supported=" + formatSupport); + ", supported=" + formatSupport);
@ -152,7 +154,7 @@ import java.util.Locale;
} }
} }
// Log tracks not associated with a renderer. // Log tracks not associated with a renderer.
TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups(); TrackGroupArray trackGroups = info.getUnassociatedTrackGroups();
if (trackGroups.length > 0) { if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:None ["); Log.d(TAG, " Renderer:None [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {

View File

@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; 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.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; 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.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; 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.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.DebugTextViewHelper;
import com.google.android.exoplayer2.ui.PlaybackControlView; import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView; import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 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.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler; import java.net.CookieHandler;
@ -77,7 +78,7 @@ import java.util.UUID;
* An activity that plays media using {@link SimpleExoPlayer}. * An activity that plays media using {@link SimpleExoPlayer}.
*/ */
public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener, 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_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
public static final String DRM_LICENSE_URL = "drm_license_url"; 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 TextView debugTextView;
private Button retryButton; private Button retryButton;
private String userAgent;
private DataSource.Factory mediaDataSourceFactory; private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private MappingTrackSelector trackSelector; private MappingTrackSelector trackSelector;
@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
shouldAutoPlay = true; shouldAutoPlay = true;
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
mediaDataSourceFactory = buildDataSourceFactory(true); mediaDataSourceFactory = buildDataSourceFactory(true);
mainHandler = new Handler(); mainHandler = new Handler();
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
initializePlayer(); initializePlayer();
} else if (view.getParent() == debugRootView) { } else if (view.getParent() == debugRootView) {
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(), 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); boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA) UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null; ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
DrmSessionManager drmSessionManager = null; DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
if (drmSchemeUuid != null) { if (drmSchemeUuid != null) {
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL); String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES); 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 int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment()); : uri.getLastPathSegment());
switch (type) { switch (type) {
case Util.TYPE_SS: case C.TYPE_SS:
return new SsMediaSource(uri, buildDataSourceFactory(false), return new SsMediaSource(uri, buildDataSourceFactory(false),
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
case Util.TYPE_DASH: case C.TYPE_DASH:
return new DashMediaSource(uri, buildDataSourceFactory(false), return new DashMediaSource(uri, buildDataSourceFactory(false),
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
case Util.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger); return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
case Util.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, eventLogger); mainHandler, eventLogger);
default: { default: {
@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
} }
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
Map<String, String> keyRequestProperties) String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
throws UnsupportedDrmException {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
return null; return null;
} }
@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
* @return A new DataSource factory. * @return A new DataSource factory.
*/ */
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
return new DefaultDataSourceFactory(this, useBandwidthMeter ? BANDWIDTH_METER : null, return ((DemoApplication) getApplication())
buildHttpDataSourceFactory(useBandwidthMeter)); .buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
} }
/** /**
@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
* @return A new HttpDataSource factory. * @return A new HttpDataSource factory.
*/ */
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { 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 // ExoPlayer.EventListener implementation
@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
// MappingTrackSelector.EventListener implementation // MappingTrackSelector.EventListener implementation
@Override @Override
public void onTracksChanged(TrackInfo trackInfo) { public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
updateButtonVisibilities(); updateButtonVisibilities();
MappedTrackInfo trackInfo = trackSelections.info;
if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) {
showToast(R.string.error_unsupported_video); showToast(R.string.error_unsupported_video);
} }
@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
return; return;
} }
TrackInfo trackInfo = trackSelector.getTrackInfo(); TrackSelections<MappedTrackInfo> trackSelections = trackSelector.getCurrentSelections();
if (trackInfo == null) { if (trackSelections == null) {
return; return;
} }
int rendererCount = trackInfo.rendererCount; int rendererCount = trackSelections.length;
for (int i = 0; i < rendererCount; i++) { for (int i = 0; i < rendererCount; i++) {
TrackGroupArray trackGroups = trackInfo.getTrackGroups(i); TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i);
if (trackGroups.length != 0) { if (trackGroups.length != 0) {
Button button = new Button(this); Button button = new Button(this);
int label; int label;

View File

@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; 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.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
@ -51,7 +51,7 @@ import java.util.Locale;
private final MappingTrackSelector selector; private final MappingTrackSelector selector;
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private TrackInfo trackInfo; private MappedTrackInfo trackInfo;
private int rendererIndex; private int rendererIndex;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private boolean[] trackGroupsAdaptive; private boolean[] trackGroupsAdaptive;
@ -82,7 +82,7 @@ import java.util.Locale;
* @param trackInfo The current track information. * @param trackInfo The current track information.
* @param rendererIndex The index of the renderer. * @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) { int rendererIndex) {
this.trackInfo = trackInfo; this.trackInfo = trackInfo;
this.rendererIndex = rendererIndex; this.rendererIndex = rendererIndex;
@ -203,11 +203,7 @@ import java.util.Locale;
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (isDisabled) { selector.setRendererDisabled(rendererIndex, isDisabled);
selector.setRendererDisabled(rendererIndex, true);
return;
}
selector.setRendererDisabled(rendererIndex, false);
if (override != null) { if (override != null) {
selector.setSelectionOverride(rendererIndex, trackGroups, override); selector.setSelectionOverride(rendererIndex, trackGroups, override);
} else { } else {

View File

@ -100,7 +100,8 @@ public final class CronetDataSourceTest {
Executor executor, int priority, Executor executor, int priority,
Collection<Object> connectionAnnotations, Collection<Object> connectionAnnotations,
boolean disableCache, boolean disableCache,
boolean disableConnectionMigration); boolean disableConnectionMigration,
boolean allowDirectExecutor);
} }
@Mock @Mock
@ -108,7 +109,7 @@ public final class CronetDataSourceTest {
@Mock @Mock
private Predicate<String> mockContentTypePredicate; private Predicate<String> mockContentTypePredicate;
@Mock @Mock
private TransferListener mockTransferListener; private TransferListener<CronetDataSource> mockTransferListener;
@Mock @Mock
private Clock mockClock; private Clock mockClock;
@Mock @Mock
@ -143,6 +144,7 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest); any(Boolean.class))).thenReturn(mockUrlRequest);
mockStatusResponse(); mockStatusResponse();
@ -170,8 +172,8 @@ public final class CronetDataSourceTest {
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void testOpeningTwiceThrows() throws HttpDataSourceException, IllegalStateException { public void testOpeningTwiceThrows() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
assertConnectionState(CronetDataSource.IDLE_CONNECTION); assertConnectionState(CronetDataSource.IDLE_CONNECTION);
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
@ -181,7 +183,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testCallbackFromPreviousRequest() throws HttpDataSourceException { public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
dataSourceUnderTest.close(); dataSourceUnderTest.close();
@ -194,6 +196,7 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest2); any(Boolean.class))).thenReturn(mockUrlRequest2);
doAnswer(new Answer<Object>() { doAnswer(new Answer<Object>() {
@Override @Override
@ -214,7 +217,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestStartCalled() throws HttpDataSourceException { public void testRequestStartCalled() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
verify(mockCronetEngine).createRequest( verify(mockCronetEngine).createRequest(
@ -224,13 +227,14 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class)); any(Boolean.class));
verify(mockUrlRequest).start(); verify(mockUrlRequest).start();
} }
@Test @Test
public void testRequestHeadersSet() throws HttpDataSourceException { public void testRequestHeadersSet() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
testResponseHeader.put("Content-Length", Long.toString(5000L)); testResponseHeader.put("Content-Length", Long.toString(5000L));
@ -248,13 +252,29 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpen() throws HttpDataSourceException { public void testRequestOpen() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec)); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec));
assertConnectionState(CronetDataSource.OPEN_CONNECTION); assertConnectionState(CronetDataSource.OPEN_CONNECTION);
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); 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 @Test
public void testRequestOpenFail() { public void testRequestOpenFail() {
mockResponseStartFailure(); mockResponseStartFailure();
@ -291,7 +311,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesStatusCode() { public void testRequestOpenValidatesStatusCode() {
mockResponesStartSuccess(); mockResponseStartSuccess();
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
try { try {
@ -308,7 +328,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesContentTypePredicate() { public void testRequestOpenValidatesContentTypePredicate() {
mockResponesStartSuccess(); mockResponseStartSuccess();
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false);
try { try {
@ -325,7 +345,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesContentLength() { public void testRequestOpenValidatesContentLength() {
mockResponesStartSuccess(); mockResponseStartSuccess();
// Data spec's requested length, 5000. Test response's length, 16,000. // Data spec's requested length, 5000. Test response's length, 16,000.
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
@ -344,7 +364,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpen() throws HttpDataSourceException { public void testPostRequestOpen() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec)); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec));
@ -354,7 +374,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpenValidatesContentType() { public void testPostRequestOpenValidatesContentType() {
mockResponesStartSuccess(); mockResponseStartSuccess();
try { try {
dataSourceUnderTest.open(testPostDataSpec); dataSourceUnderTest.open(testPostDataSpec);
@ -366,7 +386,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpenRejects307Redirects() { public void testPostRequestOpenRejects307Redirects() {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockResponseStartRedirect(); mockResponseStartRedirect();
try { try {
@ -380,7 +400,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestReadTwice() throws HttpDataSourceException { public void testRequestReadTwice() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
@ -402,7 +422,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testSecondRequestNoContentLength() throws HttpDataSourceException { public void testSecondRequestNoContentLength() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
byte[] returnedBuffer = new byte[8]; byte[] returnedBuffer = new byte[8];
@ -433,7 +453,23 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadWithOffset() throws HttpDataSourceException { 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(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
@ -447,7 +483,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadReturnsWhatItCan() throws HttpDataSourceException { public void testReadReturnsWhatItCan() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
@ -461,7 +497,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testClosedMeansClosed() throws HttpDataSourceException { public void testClosedMeansClosed() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
int bytesRead = 0; int bytesRead = 0;
@ -489,7 +525,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testOverread() throws HttpDataSourceException { public void testOverread() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
// Ask for 16 bytes // Ask for 16 bytes
@ -676,7 +712,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testExceptionFromTransferListener() throws HttpDataSourceException { public void testExceptionFromTransferListener() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that // Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
// the subsequent open() call succeeds. // the subsequent open() call succeeds.
@ -695,7 +731,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadFailure() throws HttpDataSourceException { public void testReadFailure() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadFailure(); mockReadFailure();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
@ -722,7 +758,7 @@ public final class CronetDataSourceTest {
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class)); }).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
} }
private void mockResponesStartSuccess() { private void mockResponseStartSuccess() {
doAnswer(new Answer<Object>() { doAnswer(new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {

View File

@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
try { try {
validateResponse(info); validateResponse(info);
responseInfo = info; responseInfo = info;
// Check content length.
contentLength = getContentLength(info.getAllHeaders()); if (isCompressed(info)) {
// If a specific length is requested and a specific length is returned but the 2 don't match contentLength = currentDataSpec.length;
// it's an error. } else {
if (currentDataSpec.length != C.LENGTH_UNSET // Check content length.
&& contentLength != C.LENGTH_UNSET contentLength = getContentLength(info.getAllHeaders());
&& currentDataSpec.length != contentLength) { // If a specific length is requested and a specific length is returned but the 2 don't match
throw new OpenException("Content length did not match requested length", currentDataSpec, // it's an error.
getCurrentRequestStatus()); 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) { 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 { private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
// Check for a valid response code. // Check for a valid response code.
int responseCode = info.getHttpStatusCode(); int responseCode = info.getHttpStatusCode();

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); 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 = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(

View File

@ -55,6 +55,7 @@ dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'org.mockito:mockito-core:1.9.5' androidTestCompile 'org.mockito:mockito-core:1.9.5'
compile 'com.android.support:support-annotations:24.2.0'
} }
android.libraryVariants.all { variant -> android.libraryVariants.all { variant ->
@ -95,7 +96,7 @@ publish {
userOrg = 'google' userOrg = 'google'
groupId = 'com.google.android.exoplayer' groupId = 'com.google.android.exoplayer'
artifactId = 'exoplayer' artifactId = 'exoplayer'
version = 'r2.0.0' version = 'r2.0.1'
description = 'The ExoPlayer library.' description = 'The ExoPlayer library.'
website = 'https://github.com/google/ExoPlayer' website = 'https://github.com/google/ExoPlayer'
} }

Binary file not shown.

View 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

View 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

View 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

View 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

View File

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

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = application/eia-608 sampleMimeType = application/cea-608
maxInputSize = -1 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
@ -25,305 +25,605 @@ track 0:
language = null language = null
drmInitData = - drmInitData = -
initializationData: initializationData:
sample count = 75 sample count = 150
sample 0: sample 0:
time = 37657512133 time = 37657512133
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 1: sample 1:
time = 37657528822
flags = 1
data = length 3, hash 7724
sample 2:
time = 37657545511 time = 37657545511
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 3, hash 766F
sample 2: sample 3:
time = 37657562177
flags = 1
data = length 3, hash 7724
sample 4:
time = 37657578866 time = 37657578866
flags = 1 flags = 1
data = length 2, hash FFFFF6DC data = length 3, hash 767E
sample 3: sample 5:
time = 37657595555
flags = 1
data = length 3, hash 7724
sample 6:
time = 37657612244 time = 37657612244
flags = 1 flags = 1
data = length 2, hash FFFFF65B data = length 15, hash E4359178
sample 4: sample 7:
time = 37657628911
flags = 1
data = length 3, hash 7724
sample 8:
time = 37657645600 time = 37657645600
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 12, hash 15EBEB66
sample 5: sample 9:
time = 37657662288
flags = 1
data = length 3, hash 7724
sample 10:
time = 37657678977 time = 37657678977
flags = 1 flags = 1
data = length 2, hash FFFFF67B data = length 3, hash 761D
sample 6: sample 11:
time = 37657695644
flags = 1
data = length 3, hash 7724
sample 12:
time = 37657712333 time = 37657712333
flags = 1 flags = 1
data = length 2, hash 2B5 data = length 30, hash E181418F
sample 7: sample 13:
time = 37657729022
flags = 1
data = length 6, hash 36289CE2
sample 14:
time = 37657745711 time = 37657745711
flags = 1 flags = 1
data = length 2, hash F5 data = length 12, hash 3C304F5B
sample 8: sample 15:
time = 37657762377
flags = 1
data = length 3, hash 7724
sample 16:
time = 37657779066 time = 37657779066
flags = 1 flags = 1
data = length 2, hash FFFFF87A data = length 12, hash 88DD8EF6
sample 9: sample 17:
time = 37657795755
flags = 1
data = length 3, hash 7724
sample 18:
time = 37657812444 time = 37657812444
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 12, hash 8B411833
sample 10: sample 19:
time = 37657829111
flags = 1
data = length 3, hash 7724
sample 20:
time = 37657845800 time = 37657845800
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 12, hash 742A2DF1
sample 11: sample 21:
time = 37657862488
flags = 1
data = length 3, hash 7724
sample 22:
time = 37657879177 time = 37657879177
flags = 1 flags = 1
data = length 2, hash 803 data = length 12, hash 9A2ECBEE
sample 12: sample 23:
time = 37657895844
flags = 1
data = length 3, hash 7724
sample 24:
time = 37657912533 time = 37657912533
flags = 1 flags = 1
data = length 2, hash 1F8 data = length 12, hash 562688EA
sample 13: sample 25:
time = 37657929222
flags = 1
data = length 3, hash 7724
sample 26:
time = 37657945911 time = 37657945911
flags = 1 flags = 1
data = length 2, hash 117A data = length 12, hash ADE4B953
sample 14: sample 27:
time = 37657962577
flags = 1
data = length 3, hash 7724
sample 28:
time = 37657979266 time = 37657979266
flags = 1 flags = 1
data = length 2, hash 166 data = length 12, hash F927E3E5
sample 15: sample 29:
time = 37657995955
flags = 1
data = length 3, hash 7724
sample 30:
time = 37658012644 time = 37658012644
flags = 1 flags = 1
data = length 2, hash 105A data = length 12, hash EA327945
sample 16: sample 31:
time = 37658029311
flags = 1
data = length 3, hash 7724
sample 32:
time = 37658046000 time = 37658046000
flags = 1 flags = 1
data = length 2, hash FCF data = length 12, hash 3E5DA13C
sample 17: sample 33:
time = 37658062688
flags = 1
data = length 3, hash 7724
sample 34:
time = 37658079377 time = 37658079377
flags = 1 flags = 1
data = length 2, hash 1253 data = length 12, hash BF646AE3
sample 18: sample 35:
time = 37658096044
flags = 1
data = length 3, hash 7724
sample 36:
time = 37658112733 time = 37658112733
flags = 1 flags = 1
data = length 2, hash 11DA data = length 12, hash 41E3BA78
sample 19: sample 37:
time = 37658129422
flags = 1
data = length 3, hash 7724
sample 38:
time = 37658146111 time = 37658146111
flags = 1 flags = 1
data = length 2, hash 795 data = length 12, hash A2945EF6
sample 20: sample 39:
time = 37658162777
flags = 1
data = length 3, hash 7724
sample 40:
time = 37658179466 time = 37658179466
flags = 1 flags = 1
data = length 2, hash 103E data = length 12, hash 26735812
sample 21: sample 41:
time = 37658196155
flags = 1
data = length 3, hash 7724
sample 42:
time = 37658212844 time = 37658212844
flags = 1 flags = 1
data = length 2, hash 120F data = length 12, hash DC14D3D8
sample 22: sample 43:
time = 37658229511
flags = 1
data = length 3, hash 7724
sample 44:
time = 37658246200 time = 37658246200
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 12, hash 882191BE
sample 23: sample 45:
time = 37658262888
flags = 1
data = length 3, hash 7724
sample 46:
time = 37658279577 time = 37658279577
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 12, hash 8B4886B1
sample 24: sample 47:
time = 37658296244
flags = 1
data = length 3, hash 7724
sample 48:
time = 37658312933 time = 37658312933
flags = 1 flags = 1
data = length 2, hash FFFFF71B data = length 12, hash 98D98F96
sample 25: sample 49:
time = 37658329622
flags = 1
data = length 3, hash 7724
sample 50:
time = 37658346311 time = 37658346311
flags = 1 flags = 1
data = length 2, hash F91 data = length 30, hash CF8E53E3
sample 26: sample 51:
time = 37658362977
flags = 1
data = length 6, hash 36289CE2
sample 52:
time = 37658379666 time = 37658379666
flags = 1 flags = 1
data = length 2, hash 166 data = length 12, hash F883C9EE
sample 27: sample 53:
time = 37658396355
flags = 1
data = length 3, hash 7724
sample 54:
time = 37658413044 time = 37658413044
flags = 1 flags = 1
data = length 2, hash 1023 data = length 12, hash 6E6B2B9C
sample 28: sample 55:
time = 37658429711
flags = 1
data = length 3, hash 7724
sample 56:
time = 37658446400 time = 37658446400
flags = 1 flags = 1
data = length 2, hash 117A data = length 12, hash B4FE7F08
sample 29: sample 57:
time = 37658463088
flags = 1
data = length 3, hash 7724
sample 58:
time = 37658479777 time = 37658479777
flags = 1 flags = 1
data = length 2, hash 784 data = length 12, hash 5A1EA7C7
sample 30: sample 59:
time = 37658496444
flags = 1
data = length 3, hash 7724
sample 60:
time = 37658513133 time = 37658513133
flags = 1 flags = 1
data = length 2, hash 1F8 data = length 12, hash 46BD6CC9
sample 31: sample 61:
time = 37658529822
flags = 1
data = length 3, hash 7724
sample 62:
time = 37658546511 time = 37658546511
flags = 1 flags = 1
data = length 2, hash 10D9 data = length 12, hash 1B1E2554
sample 32: sample 63:
time = 37658563177
flags = 1
data = length 3, hash 7724
sample 64:
time = 37658579866 time = 37658579866
flags = 1 flags = 1
data = length 2, hash 935 data = length 12, hash 91FCC537
sample 33: sample 65:
time = 37658596555
flags = 1
data = length 3, hash 7724
sample 66:
time = 37658613244 time = 37658613244
flags = 1 flags = 1
data = length 2, hash 2B5 data = length 12, hash A9355E1B
sample 34: sample 67:
time = 37658629911
flags = 1
data = length 3, hash 7724
sample 68:
time = 37658646600 time = 37658646600
flags = 1 flags = 1
data = length 2, hash F5 data = length 12, hash 2511F69B
sample 35: sample 69:
time = 37658663288
flags = 1
data = length 3, hash 7724
sample 70:
time = 37658679977 time = 37658679977
flags = 1 flags = 1
data = length 2, hash FFFFF87A data = length 12, hash 90925736
sample 36: sample 71:
time = 37658696644
flags = 1
data = length 3, hash 7724
sample 72:
time = 37658713333 time = 37658713333
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 21, hash 431EEE30
sample 37: sample 73:
time = 37658730022
flags = 1
data = length 3, hash 7724
sample 74:
time = 37658746711 time = 37658746711
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 12, hash 7BDEF631
sample 38: sample 75:
time = 37658763377
flags = 1
data = length 3, hash 7724
sample 76:
time = 37658780066 time = 37658780066
flags = 1 flags = 1
data = length 2, hash 793 data = length 12, hash A2EEF59E
sample 39: sample 77:
time = 37658796755
flags = 1
data = length 3, hash 7724
sample 78:
time = 37658813444 time = 37658813444
flags = 1 flags = 1
data = length 2, hash FF0 data = length 12, hash BFC6C022
sample 40: sample 79:
time = 37658830111
flags = 1
data = length 3, hash 7724
sample 80:
time = 37658846800 time = 37658846800
flags = 1 flags = 1
data = length 2, hash 16B data = length 12, hash CD4D8FCA
sample 41: sample 81:
time = 37658863488
flags = 1
data = length 3, hash 7724
sample 82:
time = 37658880177 time = 37658880177
flags = 1 flags = 1
data = length 2, hash 2C0 data = length 12, hash 2BDE8EFA
sample 42: sample 83:
time = 37658896844
flags = 1
data = length 3, hash 7724
sample 84:
time = 37658913533 time = 37658913533
flags = 1 flags = 1
data = length 2, hash FFFFF953 data = length 12, hash 8C858812
sample 43: sample 85:
time = 37658930222
flags = 1
data = length 3, hash 7724
sample 86:
time = 37658946911 time = 37658946911
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash DE7D0E31
sample 44: sample 87:
time = 37658963577
flags = 1
data = length 3, hash 7724
sample 88:
time = 37658980266 time = 37658980266
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 45: sample 89:
time = 37658996955
flags = 1
data = length 3, hash 7724
sample 90:
time = 37659013644 time = 37659013644
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 46: sample 91:
time = 37659030311
flags = 1
data = length 3, hash 7724
sample 92:
time = 37659047000 time = 37659047000
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 47: sample 93:
time = 37659063688
flags = 1
data = length 3, hash 7724
sample 94:
time = 37659080377 time = 37659080377
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 48: sample 95:
time = 37659097044
flags = 1
data = length 3, hash 7724
sample 96:
time = 37659113733 time = 37659113733
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 49: sample 97:
time = 37659130422
flags = 1
data = length 3, hash 7724
sample 98:
time = 37659147111 time = 37659147111
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 50: sample 99:
time = 37659163777
flags = 1
data = length 3, hash 7724
sample 100:
time = 37659180466 time = 37659180466
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 51: sample 101:
time = 37659197155
flags = 1
data = length 3, hash 7724
sample 102:
time = 37659213844 time = 37659213844
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 52: sample 103:
time = 37659230511
flags = 1
data = length 3, hash 7724
sample 104:
time = 37659247200 time = 37659247200
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 53: sample 105:
time = 37659263888
flags = 1
data = length 3, hash 7724
sample 106:
time = 37659280577 time = 37659280577
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 54: sample 107:
time = 37659297244
flags = 1
data = length 3, hash 7724
sample 108:
time = 37659313933 time = 37659313933
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 55: sample 109:
time = 37659330622
flags = 1
data = length 3, hash 7724
sample 110:
time = 37659347311 time = 37659347311
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 56: sample 111:
time = 37659363977
flags = 1
data = length 3, hash 7724
sample 112:
time = 37659380666 time = 37659380666
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 57: sample 113:
time = 37659397355
flags = 1
data = length 3, hash 7724
sample 114:
time = 37659414044 time = 37659414044
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 58: sample 115:
time = 37659430711
flags = 1
data = length 3, hash 7724
sample 116:
time = 37659447400 time = 37659447400
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 59: sample 117:
time = 37659464088
flags = 1
data = length 3, hash 7724
sample 118:
time = 37659480777 time = 37659480777
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 60: sample 119:
time = 37659497444
flags = 1
data = length 3, hash 7724
sample 120:
time = 37659514133 time = 37659514133
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 61: sample 121:
time = 37659530822
flags = 1
data = length 3, hash 7724
sample 122:
time = 37659547511 time = 37659547511
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 62: sample 123:
time = 37659564177
flags = 1
data = length 3, hash 7724
sample 124:
time = 37659580866 time = 37659580866
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7363
sample 63: sample 125:
time = 37659597555
flags = 1
data = length 3, hash 7724
sample 126:
time = 37659614244 time = 37659614244
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 3, hash 766F
sample 64: sample 127:
time = 37659630911
flags = 1
data = length 3, hash 7724
sample 128:
time = 37659647600 time = 37659647600
flags = 1 flags = 1
data = length 2, hash FFFFF6DC data = length 3, hash 767E
sample 65: sample 129:
time = 37659664288
flags = 1
data = length 3, hash 7724
sample 130:
time = 37659680977 time = 37659680977
flags = 1 flags = 1
data = length 2, hash FFFFF65B data = length 15, hash 191B585A
sample 66: sample 131:
time = 37659697644
flags = 1
data = length 3, hash 7724
sample 132:
time = 37659714333 time = 37659714333
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 12, hash 15EC5FC5
sample 67: sample 133:
time = 37659731022
flags = 1
data = length 3, hash 7724
sample 134:
time = 37659747711 time = 37659747711
flags = 1 flags = 1
data = length 2, hash FFFFF6FF data = length 3, hash 76A1
sample 68: sample 135:
time = 37659764377
flags = 1
data = length 3, hash 7724
sample 136:
time = 37659781066 time = 37659781066
flags = 1 flags = 1
data = length 2, hash FFFFF6AC data = length 30, hash E8012479
sample 69: sample 137:
time = 37659797755
flags = 1
data = length 6, hash 36289D5E
sample 138:
time = 37659814444 time = 37659814444
flags = 1 flags = 1
data = length 2, hash FFFFF5FE data = length 12, hash D32F29F3
sample 70: sample 139:
time = 37659831111
flags = 1
data = length 3, hash 7724
sample 140:
time = 37659847800 time = 37659847800
flags = 1 flags = 1
data = length 2, hash FFFFFEF7 data = length 21, hash 6258623
sample 71: sample 141:
time = 37659864488
flags = 1
data = length 3, hash 7724
sample 142:
time = 37659881177 time = 37659881177
flags = 1 flags = 1
data = length 2, hash 120C data = length 12, hash FE69ABA2
sample 72: sample 143:
time = 37659897844
flags = 1
data = length 3, hash 7724
sample 144:
time = 37659914533 time = 37659914533
flags = 1 flags = 1
data = length 2, hash 1124 data = length 12, hash 958D0815
sample 73: sample 145:
time = 37659931222
flags = 1
data = length 3, hash 7724
sample 146:
time = 37659947911 time = 37659947911
flags = 1 flags = 1
data = length 2, hash 1A9 data = length 12, hash FF57BFD8
sample 74: sample 147:
time = 37659964577
flags = 1
data = length 3, hash 7724
sample 148:
time = 37659981266 time = 37659981266
flags = 1 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 tracksEnded = true

View File

@ -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 byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};
private static final int LARGE_TEST_DATA_LENGTH = 8192; private static final int LARGE_TEST_DATA_LENGTH = 8192;
public void testInitialPosition() throws IOException { public void testInitialPosition() throws Exception {
FakeDataSource testDataSource = buildDataSource(); FakeDataSource testDataSource = buildDataSource();
DefaultExtractorInput input = DefaultExtractorInput input =
new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET); new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET);
assertEquals(123, input.getPosition()); assertEquals(123, input.getPosition());
} }
public void testRead() throws IOException, InterruptedException { public void testRead() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
// We expect to perform three reads of three bytes, as setup in buildTestDataSource. // 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); assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
} }
public void testReadPeeked() throws IOException, InterruptedException { public void testReadPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
@ -71,7 +71,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertTrue(Arrays.equals(TEST_DATA, target)); assertTrue(Arrays.equals(TEST_DATA, target));
} }
public void testReadMoreDataPeeked() throws IOException, InterruptedException { public void testReadMoreDataPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
@ -84,7 +84,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertTrue(Arrays.equals(TEST_DATA, target)); assertTrue(Arrays.equals(TEST_DATA, target));
} }
public void testReadFullyOnce() throws IOException, InterruptedException { public void testReadFullyOnce() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
input.readFully(target, 0, 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. // Read TEST_DATA in two parts.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[5]; byte[] target = new byte[5];
@ -116,7 +116,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(5 + 4, input.getPosition()); 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. // Read more than TEST_DATA. Should fail with an EOFException. Position should not update.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
try { try {
@ -141,7 +141,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(0, input.getPosition()); assertEquals(0, input.getPosition());
} }
public void testReadFullyWithFailingDataSource() throws IOException, InterruptedException { public void testReadFullyWithFailingDataSource() throws Exception {
FakeDataSource testDataSource = buildFailingDataSource(); FakeDataSource testDataSource = buildFailingDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
try { try {
@ -155,7 +155,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(0, input.getPosition()); assertEquals(0, input.getPosition());
} }
public void testReadFullyHalfPeeked() throws IOException, InterruptedException { public void testReadFullyHalfPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
@ -168,7 +168,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(TEST_DATA.length, input.getPosition()); assertEquals(TEST_DATA.length, input.getPosition());
} }
public void testSkip() throws IOException, InterruptedException { public void testSkip() throws Exception {
FakeDataSource testDataSource = buildDataSource(); FakeDataSource testDataSource = buildDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
// We expect to perform three skips of three bytes, as setup in buildTestDataSource. // 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); assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
} }
public void testLargeSkip() throws IOException, InterruptedException { public void testLargeSkip() throws Exception {
FakeDataSource testDataSource = buildLargeDataSource(); FakeDataSource testDataSource = buildLargeDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
// Check that skipping the entire data source succeeds. // 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. // Skip TEST_DATA.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(TEST_DATA.length); 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. // Skip TEST_DATA in two parts.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(5); input.skipFully(5);
@ -216,7 +216,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(5 + 4, input.getPosition()); assertEquals(5 + 4, input.getPosition());
} }
public void testSkipFullyTwicePeeked() throws IOException, InterruptedException { public void testSkipFullyTwicePeeked() throws Exception {
// Skip TEST_DATA. // Skip TEST_DATA.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
@ -230,7 +230,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(TEST_DATA.length, input.getPosition()); 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. // Skip more than TEST_DATA. Should fail with an EOFException. Position should not update.
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
try { try {
@ -253,7 +253,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(0, input.getPosition()); assertEquals(0, input.getPosition());
} }
public void testSkipFullyWithFailingDataSource() throws IOException, InterruptedException { public void testSkipFullyWithFailingDataSource() throws Exception {
FakeDataSource testDataSource = buildFailingDataSource(); FakeDataSource testDataSource = buildFailingDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
try { try {
@ -266,7 +266,7 @@ public class DefaultExtractorInputTest extends TestCase {
assertEquals(0, input.getPosition()); 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. // Tests skipping an amount of data that's larger than any internal scratch space.
int largeSkipSize = 1024 * 1024; int largeSkipSize = 1024 * 1024;
FakeDataSource.Builder builder = new FakeDataSource.Builder(); 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(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
input.peekFully(target, 0, 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(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
input.peekFully(target, 0, TEST_DATA.length); input.peekFully(target, 0, TEST_DATA.length);
@ -336,8 +336,7 @@ public class DefaultExtractorInputTest extends TestCase {
} }
} }
public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception {
throws IOException, InterruptedException {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
@ -348,8 +347,24 @@ public class DefaultExtractorInputTest extends TestCase {
assertFalse(input.peekFully(target, 0, 1, true)); assertFalse(input.peekFully(target, 0, 1, true));
} }
public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() public void testPeekFullyAtEndThenReadEndOfInput() throws Exception {
throws IOException, InterruptedException { 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(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; byte[] target = new byte[TEST_DATA.length];
@ -365,8 +380,7 @@ public class DefaultExtractorInputTest extends TestCase {
} }
} }
public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception {
throws IOException, InterruptedException {
DefaultExtractorInput input = createDefaultExtractorInput(); DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length]; 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(); FakeDataSource.Builder builder = new FakeDataSource.Builder();
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3));
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6));
@ -392,7 +406,7 @@ public class DefaultExtractorInputTest extends TestCase {
return testDataSource; return testDataSource;
} }
private static FakeDataSource buildFailingDataSource() throws IOException { private static FakeDataSource buildFailingDataSource() throws Exception {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource.Builder builder = new FakeDataSource.Builder();
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6)); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6));
builder.appendReadError(new IOException()); builder.appendReadError(new IOException());
@ -402,7 +416,7 @@ public class DefaultExtractorInputTest extends TestCase {
return testDataSource; return testDataSource;
} }
private static FakeDataSource buildLargeDataSource() throws IOException { private static FakeDataSource buildLargeDataSource() throws Exception {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource.Builder builder = new FakeDataSource.Builder();
builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
FakeDataSource testDataSource = builder.build(); FakeDataSource testDataSource = builder.build();
@ -410,8 +424,9 @@ public class DefaultExtractorInputTest extends TestCase {
return testDataSource; return testDataSource;
} }
private static DefaultExtractorInput createDefaultExtractorInput() throws IOException { private static DefaultExtractorInput createDefaultExtractorInput() throws Exception {
FakeDataSource testDataSource = buildDataSource(); FakeDataSource testDataSource = buildDataSource();
return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
} }
} }

View File

@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
}, "mp3/bear.mp3", getInstrumentation()); }, "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());
}
} }

View File

@ -16,8 +16,16 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; 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.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Random; import java.util.Random;
@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase {
}, "ts/sample.ts", fileData, getInstrumentation()); }, "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 { private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (((byte) i) == TS_SYNC_BYTE) { 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);
}
}
}
} }

View File

@ -17,8 +17,11 @@ package com.google.android.exoplayer2;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.support.annotation.IntDef;
import android.view.Surface; import android.view.Surface;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID; import java.util.UUID;
/** /**
@ -70,55 +73,79 @@ public final class C {
*/ */
public static final String UTF8_NAME = "UTF-8"; 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 * @see MediaCodec#CRYPTO_MODE_AES_CTR
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; 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 * @see AudioFormat#ENCODING_INVALID
*/ */
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/** /**
* @see AudioFormat#ENCODING_PCM_8BIT * @see AudioFormat#ENCODING_PCM_8BIT
*/ */
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/** /**
* @see AudioFormat#ENCODING_PCM_16BIT * @see AudioFormat#ENCODING_PCM_16BIT
*/ */
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/** /**
* PCM encoding with 24 bits per sample. * PCM encoding with 24 bits per sample.
*/ */
public static final int ENCODING_PCM_24BIT = 0x80000000; public static final int ENCODING_PCM_24BIT = 0x80000000;
/** /**
* PCM encoding with 32 bits per sample. * PCM encoding with 32 bits per sample.
*/ */
public static final int ENCODING_PCM_32BIT = 0x40000000; public static final int ENCODING_PCM_32BIT = 0x40000000;
/** /**
* @see AudioFormat#ENCODING_AC3 * @see AudioFormat#ENCODING_AC3
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/** /**
* @see AudioFormat#ENCODING_E_AC3 * @see AudioFormat#ENCODING_E_AC3
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
/** /**
* @see AudioFormat#ENCODING_DTS * @see AudioFormat#ENCODING_DTS
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
/** /**
* @see AudioFormat#ENCODING_DTS_HD * @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 public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; ? 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. * Indicates that a buffer holds a synchronization sample.
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; 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. * Flag for empty buffers that signal that the end of the stream was reached.
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/** /**
* Indicates that a buffer is (at least partially) encrypted. * Indicates that a buffer is (at least partially) encrypted.
*/ */
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000; public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
/** /**
* Indicates that a buffer should be decoded but not rendered. * Indicates that a buffer should be decoded but not rendered.
*/ */
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000; 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. * A return value for methods where the end of an input was encountered.
*/ */
public static final int RESULT_END_OF_INPUT = -1; 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. * 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; public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
/** /**
* A return value for methods where nothing was read. * A return value for methods where nothing was read.
*/ */
public static final int RESULT_NOTHING_READ = -3; public static final int RESULT_NOTHING_READ = -3;
/** /**
* A return value for methods where a buffer was read. * A return value for methods where a buffer was read.
*/ */
public static final int RESULT_BUFFER_READ = -4; public static final int RESULT_BUFFER_READ = -4;
/** /**
* A return value for methods where a format was read. * 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. * A data type constant for data of unknown or unspecified type.
*/ */
public static final int DATA_TYPE_UNKNOWN = 0; public static final int DATA_TYPE_UNKNOWN = 0;
/** /**
* A data type constant for media, typically containing media samples. * A data type constant for media, typically containing media samples.
*/ */
public static final int DATA_TYPE_MEDIA = 1; public static final int DATA_TYPE_MEDIA = 1;
/** /**
* A data type constant for media, typically containing only initialization data. * A data type constant for media, typically containing only initialization data.
*/ */
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2; public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
/** /**
* A data type constant for drm or encryption data. * A data type constant for drm or encryption data.
*/ */
public static final int DATA_TYPE_DRM = 3; public static final int DATA_TYPE_DRM = 3;
/** /**
* A data type constant for a manifest file. * A data type constant for a manifest file.
*/ */
public static final int DATA_TYPE_MANIFEST = 4; public static final int DATA_TYPE_MANIFEST = 4;
/** /**
* A data type constant for time synchronization data. * A data type constant for time synchronization data.
*/ */
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
/** /**
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
* equal to this value. * equal to this value.
@ -219,32 +285,26 @@ public final class C {
* A type constant for tracks of unknown type. * A type constant for tracks of unknown type.
*/ */
public static final int TRACK_TYPE_UNKNOWN = -1; public static final int TRACK_TYPE_UNKNOWN = -1;
/** /**
* A type constant for tracks of some default type, where the type itself is unknown. * A type constant for tracks of some default type, where the type itself is unknown.
*/ */
public static final int TRACK_TYPE_DEFAULT = 0; public static final int TRACK_TYPE_DEFAULT = 0;
/** /**
* A type constant for audio tracks. * A type constant for audio tracks.
*/ */
public static final int TRACK_TYPE_AUDIO = 1; public static final int TRACK_TYPE_AUDIO = 1;
/** /**
* A type constant for video tracks. * A type constant for video tracks.
*/ */
public static final int TRACK_TYPE_VIDEO = 2; public static final int TRACK_TYPE_VIDEO = 2;
/** /**
* A type constant for text tracks. * A type constant for text tracks.
*/ */
public static final int TRACK_TYPE_TEXT = 3; public static final int TRACK_TYPE_TEXT = 3;
/** /**
* A type constant for metadata tracks. * A type constant for metadata tracks.
*/ */
public static final int TRACK_TYPE_METADATA = 4; public static final int TRACK_TYPE_METADATA = 4;
/** /**
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
* equal to this value. * equal to this value.
@ -255,27 +315,22 @@ public final class C {
* A selection reason constant for selections whose reasons are unknown or unspecified. * A selection reason constant for selections whose reasons are unknown or unspecified.
*/ */
public static final int SELECTION_REASON_UNKNOWN = 0; public static final int SELECTION_REASON_UNKNOWN = 0;
/** /**
* A selection reason constant for an initial track selection. * A selection reason constant for an initial track selection.
*/ */
public static final int SELECTION_REASON_INITIAL = 1; public static final int SELECTION_REASON_INITIAL = 1;
/** /**
* A selection reason constant for an manual (i.e. user initiated) track selection. * A selection reason constant for an manual (i.e. user initiated) track selection.
*/ */
public static final int SELECTION_REASON_MANUAL = 2; public static final int SELECTION_REASON_MANUAL = 2;
/** /**
* A selection reason constant for an adaptive track selection. * A selection reason constant for an adaptive track selection.
*/ */
public static final int SELECTION_REASON_ADAPTIVE = 3; public static final int SELECTION_REASON_ADAPTIVE = 3;
/** /**
* A selection reason constant for a trick play track selection. * A selection reason constant for a trick play track selection.
*/ */
public static final int SELECTION_REASON_TRICK_PLAY = 4; public static final int SELECTION_REASON_TRICK_PLAY = 4;
/** /**
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than * Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
* or equal to this value. * or equal to this value.
@ -363,16 +418,20 @@ public final class C {
*/ */
public static final int MSG_CUSTOM_BASE = 10000; public static final int MSG_CUSTOM_BASE = 10000;
/**
* The stereo mode for 360/3D/VR videos.
*/
@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. * Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
*/ */
public static final int STEREO_MODE_MONO = 0; public static final int STEREO_MODE_MONO = 0;
/** /**
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos. * Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
*/ */
public static final int STEREO_MODE_TOP_BOTTOM = 1; public static final int STEREO_MODE_TOP_BOTTOM = 1;
/** /**
* Indicates Left-Right stereo layout, used with 360/3D/VR videos. * Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/ */

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl {
@Override @Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) { TrackSelections<?> trackSelections) {
targetBufferSize = 0; targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (trackSelections.get(i) != null) { if (trackSelections.get(i) != null) {

View File

@ -15,15 +15,24 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Thrown when a non-recoverable playback failure occurs. * Thrown when a non-recoverable playback failure occurs.
*/ */
public final class ExoPlaybackException extends Exception { 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}. * The error occurred loading data from a {@link MediaSource}.
* <p> * <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 * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
* {@link #TYPE_UNEXPECTED}. * {@link #TYPE_UNEXPECTED}.
*/ */
@Type
public final int 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); 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); super(message, cause);
this.type = type; this.type = type;
this.rendererIndex = rendererIndex; this.rendererIndex = rendererIndex;

View File

@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline == null || pendingSeekAcks > 0) { if (timeline == null || pendingSeekAcks > 0) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} else { } else {
int periodIndex = playbackInfo.periodIndex; timeline.getPeriod(playbackInfo.periodIndex, period);
timeline.getPeriod(periodIndex, period); return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
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();
} }
} }

View File

@ -27,7 +27,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.trackselection.TrackSelection; 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.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
@ -40,8 +40,8 @@ import java.io.IOException;
/** /**
* Implements the internal behavior of {@link ExoPlayerImpl}. * Implements the internal behavior of {@link ExoPlayerImpl}.
*/ */
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback, /* package */ final class ExoPlayerImplInternal<T> implements Handler.Callback,
TrackSelector.InvalidationListener, MediaSource.Listener { MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener {
/** /**
* Playback position information which is read on the application's thread by * 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 Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector<T> trackSelector;
private final LoadControl loadControl; private final LoadControl loadControl;
private final StandaloneMediaClock standaloneMediaClock; private final StandaloneMediaClock standaloneMediaClock;
private final Handler handler; private final Handler handler;
@ -128,13 +128,13 @@ import java.io.IOException;
private boolean isTimelineReady; private boolean isTimelineReady;
private boolean isTimelineEnded; private boolean isTimelineEnded;
private int bufferAheadPeriodCount; private int bufferAheadPeriodCount;
private MediaPeriodHolder playingPeriodHolder; private MediaPeriodHolder<T> playingPeriodHolder;
private MediaPeriodHolder readingPeriodHolder; private MediaPeriodHolder<T> readingPeriodHolder;
private MediaPeriodHolder loadingPeriodHolder; private MediaPeriodHolder<T> loadingPeriodHolder;
private Timeline timeline; private Timeline timeline;
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector<T> trackSelector,
LoadControl loadControl, boolean playWhenReady, Handler eventHandler, LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
PlaybackInfo playbackInfo) { PlaybackInfo playbackInfo) {
this.renderers = renderers; this.renderers = renderers;
@ -458,12 +458,11 @@ import java.io.IOException;
startRenderers(); startRenderers();
} }
} }
} else if (state == ExoPlayer.STATE_READY) { } else if (state == ExoPlayer.STATE_READY
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady) { && (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady)) {
rebuffering = playWhenReady; rebuffering = playWhenReady;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
stopRenderers(); stopRenderers();
}
} }
if (state == ExoPlayer.STATE_BUFFERING) { if (state == ExoPlayer.STATE_BUFFERING) {
@ -530,17 +529,17 @@ import java.io.IOException;
rebuffering = false; rebuffering = false;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
if (periodPositionUs == C.TIME_UNSET if (periodPositionUs == C.TIME_UNSET || (readingPeriodHolder != playingPeriodHolder
|| (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index && (periodIndex == playingPeriodHolder.index
|| (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) { || periodIndex == readingPeriodHolder.index))) {
// Clear the timeline because either the seek position is not known, or a renderer is reading // 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. // ahead to the next period and the seek is to either the playing or reading period.
periodIndex = C.INDEX_UNSET; periodIndex = C.INDEX_UNSET;
} }
// Clear the timeline, but keep the requested period if it is already prepared. // Clear the timeline, but keep the requested period if it is already prepared.
MediaPeriodHolder periodHolder = playingPeriodHolder; MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
MediaPeriodHolder newPlayingPeriodHolder = null; MediaPeriodHolder<T> newPlayingPeriodHolder = null;
while (periodHolder != null) { while (periodHolder != null) {
if (periodHolder.index == periodIndex && periodHolder.prepared) { if (periodHolder.index == periodIndex && periodHolder.prepared) {
newPlayingPeriodHolder = periodHolder; newPlayingPeriodHolder = periodHolder;
@ -672,7 +671,7 @@ import java.io.IOException;
return; return;
} }
// Reselect tracks on each period in turn, until the selection changes. // Reselect tracks on each period in turn, until the selection changes.
MediaPeriodHolder periodHolder = playingPeriodHolder; MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
boolean selectionsChangedForReadPeriod = true; boolean selectionsChangedForReadPeriod = true;
while (true) { while (true) {
if (periodHolder == null || !periodHolder.prepared) { if (periodHolder == null || !periodHolder.prepared) {
@ -691,16 +690,14 @@ import java.io.IOException;
} }
if (selectionsChangedForReadPeriod) { if (selectionsChangedForReadPeriod) {
// Release everything after the playing period because a renderer may have read data from a // Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
// track whose selection has now changed. boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
releasePeriodHoldersFrom(playingPeriodHolder.next); releasePeriodHoldersFrom(playingPeriodHolder.next);
playingPeriodHolder.next = null; playingPeriodHolder.next = null;
readingPeriodHolder = playingPeriodHolder; readingPeriodHolder = playingPeriodHolder;
loadingPeriodHolder = playingPeriodHolder; loadingPeriodHolder = playingPeriodHolder;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
// Update streams for the new selection, recreating all streams if reading ahead.
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
boolean[] streamResetFlags = new boolean[renderers.length]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags); playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
@ -739,7 +736,7 @@ import java.io.IOException;
} }
} }
} }
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData); trackSelector.onSelectionActivated(playingPeriodHolder.trackSelections);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else { } else {
// Release and re-prepare/buffer periods after the one whose selection changed. // 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), playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window),
index); index);
MediaPeriodHolder previousPeriod = playingPeriodHolder; MediaPeriodHolder<T> previousPeriodHolder = playingPeriodHolder;
boolean seenReadingPeriod = false; boolean seenReadingPeriod = false;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
while (previousPeriod.next != null) { while (previousPeriodHolder.next != null) {
MediaPeriodHolder periodHolder = previousPeriod.next; MediaPeriodHolder<T> periodHolder = previousPeriodHolder.next;
index++; index++;
timeline.getPeriod(index, period, true); timeline.getPeriod(index, period, true);
if (!periodHolder.uid.equals(period.uid)) { 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. // Update the loading period to be the latest period that is still valid.
loadingPeriodHolder = previousPeriod; loadingPeriodHolder = previousPeriodHolder;
loadingPeriodHolder.next = null; loadingPeriodHolder.next = null;
// Release the rest of the timeline. // Release the rest of the timeline.
@ -850,7 +847,7 @@ import java.io.IOException;
if (periodHolder == readingPeriodHolder) { if (periodHolder == readingPeriodHolder) {
seenReadingPeriod = true; seenReadingPeriod = true;
} }
previousPeriod = periodHolder; previousPeriodHolder = periodHolder;
} }
} else if (loadingPeriodHolder != null) { } else if (loadingPeriodHolder != null) {
Object uid = loadingPeriodHolder.uid; Object uid = loadingPeriodHolder.uid;
@ -953,10 +950,12 @@ import java.io.IOException;
periodStartPositionUs = defaultPosition.second; periodStartPositionUs = defaultPosition.second;
} }
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid; Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex,
loadControl.getAllocator(), periodStartPositionUs); loadControl.getAllocator(), periodStartPositionUs);
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, newMediaPeriod.prepare(this);
trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs); MediaPeriodHolder<T> newPeriodHolder = new MediaPeriodHolder<>(renderers,
rendererCapabilities, trackSelector, mediaSource, newMediaPeriod, newPeriodUid,
periodStartPositionUs);
timeline.getWindow(windowIndex, window); timeline.getWindow(windowIndex, window);
newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex); newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
if (loadingPeriodHolder != null) { if (loadingPeriodHolder != null) {
@ -995,19 +994,24 @@ import java.io.IOException;
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }
updateTimelineState(); updateTimelineState();
if (readingPeriodHolder == null) {
if (readingPeriodHolder.isLast) {
// The renderers have their final SampleStreams. // The renderers have their final SampleStreams.
for (Renderer renderer : enabledRenderers) {
renderer.setCurrentStreamIsFinal();
}
return; return;
} }
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) { if (!renderer.hasReadStreamToEnd()) {
return; return;
} }
} }
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections; TrackSelections<T> oldTrackSelections = readingPeriodHolder.trackSelections;
readingPeriodHolder = readingPeriodHolder.next; readingPeriodHolder = readingPeriodHolder.next;
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections; TrackSelections<T> newTrackSelections = readingPeriodHolder.trackSelections;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(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(); long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
long loadingPeriodPositionUs = rendererPositionUs long loadingPeriodPositionUs = rendererPositionUs
- loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs; - loadingPeriodHolder.rendererPositionOffsetUs;
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
setIsLoading(continueLoading); setIsLoading(continueLoading);
@ -1086,14 +1085,15 @@ import java.io.IOException;
} }
} }
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { private void releasePeriodHoldersFrom(MediaPeriodHolder<T> periodHolder) {
while (periodHolder != null) { while (periodHolder != null) {
periodHolder.release(); periodHolder.release();
periodHolder = periodHolder.next; periodHolder = periodHolder.next;
} }
} }
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { private void setPlayingPeriodHolder(MediaPeriodHolder<T> periodHolder)
throws ExoPlaybackException {
int enabledRendererCount = 0; int enabledRendererCount = 0;
boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
for (int i = 0; i < renderers.length; i++) { 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; playingPeriodHolder = periodHolder;
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); 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. * 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 MediaPeriod mediaPeriod;
public final Object uid; public final Object uid;
@ -1187,21 +1187,20 @@ import java.io.IOException;
public boolean prepared; public boolean prepared;
public boolean hasEnabledTracks; public boolean hasEnabledTracks;
public long rendererPositionOffsetUs; public long rendererPositionOffsetUs;
public MediaPeriodHolder next; public MediaPeriodHolder<T> next;
public boolean needsContinueLoading; public boolean needsContinueLoading;
private final Renderer[] renderers; private final Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector<T> trackSelector;
private final MediaSource mediaSource; private final MediaSource mediaSource;
private Object trackSelectionData; private TrackSelections<T> trackSelections;
private TrackSelectionArray trackSelections; private TrackSelections<T> periodTrackSelections;
private TrackSelectionArray periodTrackSelections;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid, TrackSelector<T> trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod,
long positionUs) { Object uid, long positionUs) {
this.renderers = renderers; this.renderers = renderers;
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
@ -1213,7 +1212,7 @@ import java.io.IOException;
startPositionUs = positionUs; startPositionUs = positionUs;
} }
public void setNext(MediaPeriodHolder next) { public void setNext(MediaPeriodHolder<T> next) {
this.next = next; this.next = next;
} }
@ -1235,14 +1234,12 @@ import java.io.IOException;
} }
public boolean selectTracks() throws ExoPlaybackException { public boolean selectTracks() throws ExoPlaybackException {
Pair<TrackSelectionArray, Object> result = TrackSelections<T> newTrackSelections = trackSelector.selectTracks(rendererCapabilities,
trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups()); mediaPeriod.getTrackGroups());
TrackSelectionArray newTrackSelections = result.first;
if (newTrackSelections.equals(periodTrackSelections)) { if (newTrackSelections.equals(periodTrackSelections)) {
return false; return false;
} }
trackSelections = newTrackSelections; trackSelections = newTrackSelections;
trackSelectionData = result.second;
return true; return true;
} }

View File

@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * 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. * 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 * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006). * 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} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -39,20 +39,6 @@ public final class Format implements Parcelable {
*/ */
public static final int NO_VALUE = -1; 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 * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples. * 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 * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}. * C#STEREO_MODE_LEFT_RIGHT}.
*/ */
@C.StereoMode
public final int stereoMode; public final int stereoMode;
/** /**
* The projection data for 360/VR video, or null if not applicable. * 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 * {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
* other media types. * other media types.
*/ */
@C.PcmEncoding
public final int pcmEncoding; public final int pcmEncoding;
/** /**
* The number of samples to trim from the start of the decoded audio stream. * 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. * Track selection flags.
*/ */
@C.SelectionFlags
public final int 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, public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int bitrate, int maxInputSize, int width, int height, float frameRate,
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio, 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, return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData, 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, public static Format createAudioContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, 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, 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, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData, 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, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int bitrate, int maxInputSize, int channelCount, int sampleRate,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags, List<byte[]> initializationData, DrmInitData drmInitData,
String language) { @C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language); sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, int bitrate, int maxInputSize, int channelCount, int sampleRate,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags, @C.PcmEncoding int pcmEncoding, List<byte[]> initializationData, DrmInitData drmInitData,
String language) { @C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
selectionFlags, language); selectionFlags, language);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, int bitrate, int maxInputSize, int channelCount, int sampleRate,
int encoderDelay, int encoderPadding, List<byte[]> initializationData, @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding,
DrmInitData drmInitData, int selectionFlags, String language) { List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, 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, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
@ -266,20 +256,21 @@ public final class Format implements Parcelable {
// Text. // Text.
public static Format createTextContainerFormat(String id, String containerMimeType, 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, 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, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null); NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
} }
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, 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, return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
drmInitData, OFFSET_SAMPLE_RELATIVE); drmInitData, OFFSET_SAMPLE_RELATIVE);
} }
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, 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) { long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, 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, 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, /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount, float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
String language, long subsampleOffsetUs, List<byte[]> initializationData, int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
DrmInitData drmInitData) { long subsampleOffsetUs, List<byte[]> initializationData, DrmInitData drmInitData) {
this.id = id; this.id = id;
this.containerMimeType = containerMimeType; this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType; this.sampleMimeType = sampleMimeType;
@ -343,6 +334,7 @@ public final class Format implements Parcelable {
this.drmInitData = drmInitData; this.drmInitData = drmInitData;
} }
@SuppressWarnings("ResourceType")
/* package */ Format(Parcel in) { /* package */ Format(Parcel in) {
id = in.readString(); id = in.readString();
containerMimeType = in.readString(); containerMimeType = in.readString();
@ -388,8 +380,8 @@ public final class Format implements Parcelable {
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData); selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
} }
public Format copyWithContainerInfo(String id, int bitrate, int width, int height, public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
int selectionFlags, String language) { @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, 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; String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; 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; String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null) DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; || this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;

View File

@ -17,8 +17,7 @@ package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
/** /**
@ -31,10 +30,10 @@ public interface LoadControl {
* *
* @param renderers The renderers. * @param renderers The renderers.
* @param trackGroups The {@link TrackGroup}s from which the selection was made. * @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, void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections); TrackSelections<?> trackSelections);
/** /**
* Called by the player when all tracks are disabled. * Called by the player when all tracks are disabled.

View File

@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager; 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.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder; 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.source.MediaSource;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextRenderer; 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.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
@ -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 * 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. * frame is rendered for the first time since a video track was selected.
*
* @param surface The {@link Surface} to which a first frame has been rendered.
*/ */
void onRenderedFirstFrame(Surface surface); void onRenderedFirstFrame();
/** /**
* Called when the renderer is disabled. * Called when a video track is no longer selected.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/ */
void onVideoDisabled(DecoderCounters counters); void onVideoTracksDisabled();
} }
@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
private final int videoRendererCount; private final int videoRendererCount;
private final int audioRendererCount; private final int audioRendererCount;
private boolean videoTracksEnabled;
private Format videoFormat; private Format videoFormat;
private Format audioFormat; private Format audioFormat;
private Surface surface;
private SurfaceHolder surfaceHolder; private SurfaceHolder surfaceHolder;
private TextureView textureView; private TextureView textureView;
private TextRenderer.Output textOutput; private TextRenderer.Output textOutput;
@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
private float volume; private float volume;
private PlaybackParamsHolder playbackParamsHolder; private PlaybackParamsHolder playbackParamsHolder;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, /* package */ SimpleExoPlayer(Context context, TrackSelector<?> trackSelector,
LoadControl loadControl, DrmSessionManager drmSessionManager, LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler(); mainHandler = new Handler();
componentListener = new ComponentListener(); componentListener = new ComponentListener();
trackSelector.addListener(componentListener);
// Build the renderers. // Build the renderers.
ArrayList<Renderer> renderersList = new ArrayList<>(); ArrayList<Renderer> renderersList = new ArrayList<>();
@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
// Internal methods. // Internal methods.
private void buildRenderers(Context context, DrmSessionManager drmSessionManager, private void buildRenderers(Context context,
ArrayList<Renderer> renderersList, long allowedVideoJoiningTimeMs) { DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList,
long allowedVideoJoiningTimeMs) {
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener, allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener,
@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
private void setVideoSurfaceInternal(Surface surface) { private void setVideoSurfaceInternal(Surface surface) {
this.surface = surface;
ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount];
int count = 0; int count = 0;
for (Renderer renderer : renderers) { for (Renderer renderer : renderers) {
@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
private final class ComponentListener implements VideoRendererEventListener, private final class ComponentListener implements VideoRendererEventListener,
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>,
SurfaceHolder.Callback, TextureView.SurfaceTextureListener { SurfaceHolder.Callback, TextureView.SurfaceTextureListener,
TrackSelector.EventListener<Object> {
// VideoRendererEventListener implementation // VideoRendererEventListener implementation
@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onRenderedFirstFrame(Surface surface) { public void onRenderedFirstFrame(Surface surface) {
if (videoListener != null) { if (videoListener != null && SimpleExoPlayer.this.surface == surface) {
videoListener.onRenderedFirstFrame(surface); videoListener.onRenderedFirstFrame();
} }
if (videoDebugListener != null) { if (videoDebugListener != null) {
videoDebugListener.onRenderedFirstFrame(surface); videoDebugListener.onRenderedFirstFrame(surface);
@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onVideoDisabled(DecoderCounters counters) { public void onVideoDisabled(DecoderCounters counters) {
if (videoListener != null) {
videoListener.onVideoDisabled(counters);
}
if (videoDebugListener != null) { if (videoDebugListener != null) {
videoDebugListener.onVideoDisabled(counters); videoDebugListener.onVideoDisabled(counters);
} }
@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer {
// Do nothing. // 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) @TargetApi(23)

View File

@ -208,7 +208,9 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack; private android.media.AudioTrack audioTrack;
private int sampleRate; private int sampleRate;
private int channelConfig; private int channelConfig;
@C.Encoding
private int sourceEncoding; private int sourceEncoding;
@C.Encoding
private int targetEncoding; private int targetEncoding;
private boolean passthrough; private boolean passthrough;
private int pcmFrameSize; 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 * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically. * suitable buffer size automatically.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding, public void configure(String mimeType, int channelCount, int sampleRate,
int specifiedBufferSize) { @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) {
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
case 1: case 1:
@ -381,7 +383,7 @@ public final class AudioTrack {
} }
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
int sourceEncoding; @C.Encoding int sourceEncoding;
if (passthrough) { if (passthrough) {
sourceEncoding = getEncodingForMimeType(mimeType); sourceEncoding = getEncodingForMimeType(mimeType);
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT } else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
@ -470,7 +472,7 @@ public final class AudioTrack {
if (keepSessionIdAudioTrack == null) { if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO; 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. int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); 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 * @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
* capacity was insufficient for the output. * 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) { ByteBuffer out) {
int offset = buffer.position(); int offset = buffer.position();
int limit = buffer.limit(); int limit = buffer.limit();
@ -1023,6 +1025,7 @@ public final class AudioTrack {
return resampledBuffer; return resampledBuffer;
} }
@C.Encoding
private static int getEncodingForMimeType(String mimeType) { private static int getEncodingForMimeType(String mimeType) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AC3: 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) { if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
return DtsUtil.parseDtsAudioSampleCount(buffer); return DtsUtil.parseDtsAudioSampleCount(buffer);
} else if (encoding == C.ENCODING_AC3) { } else if (encoding == C.ENCODING_AC3) {

View File

@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
*/ */
public abstract class Buffer { public abstract class Buffer {
@C.BufferFlags
private int flags; 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_*} * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
* constants. * constants.
*/ */
public final void setFlags(int flags) { public final void setFlags(@C.BufferFlags int flags) {
this.flags = 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 * @param flag The flag to add to this buffer's flags, which should be one of the
* {@code C.BUFFER_FLAG_*} constants. * {@code C.BUFFER_FLAG_*} constants.
*/ */
public final void addFlag(int flag) { public final void addFlag(@C.BufferFlags int flag) {
flags |= flag; flags |= flag;
} }
@ -77,7 +78,7 @@ public abstract class Buffer {
* *
* @param flag The flag to remove. * @param flag The flag to remove.
*/ */
public final void clearFlag(int flag) { public final void clearFlag(@C.BufferFlags int flag) {
flags &= ~flag; flags &= ~flag;
} }
@ -87,7 +88,7 @@ public abstract class Buffer {
* @param flag The flag to check. * @param flag The flag to check.
* @return Whether the flag is set. * @return Whether the flag is set.
*/ */
protected final boolean getFlag(int flag) { protected final boolean getFlag(@C.BufferFlags int flag) {
return (flags & flag) == flag; return (flags & flag) == flag;
} }

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.decoder; package com.google.android.exoplayer2.decoder;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /**
@ -34,6 +35,7 @@ public final class CryptoInfo {
/** /**
* @see android.media.MediaCodec.CryptoInfo#mode * @see android.media.MediaCodec.CryptoInfo#mode
*/ */
@C.CryptoMode
public int mode; public int mode;
/** /**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData * @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) * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/ */
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, 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.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData; this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData; this.numBytesOfEncryptedData = numBytesOfEncryptedData;

View File

@ -15,7 +15,10 @@
*/ */
package com.google.android.exoplayer2.decoder; package com.google.android.exoplayer2.decoder;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -23,16 +26,21 @@ import java.nio.ByteBuffer;
*/ */
public class DecoderInputBuffer extends Buffer { 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. * Disallows buffer replacement.
*/ */
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
/** /**
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}. * Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
*/ */
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
/** /**
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
*/ */
@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer {
*/ */
public long timeUs; public long timeUs;
@BufferReplacementMode
private final int 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 * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/ */
public DecoderInputBuffer(int bufferReplacementMode) { public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) {
this.cryptoInfo = new CryptoInfo(); this.cryptoInfo = new CryptoInfo();
this.bufferReplacementMode = bufferReplacementMode; this.bufferReplacementMode = bufferReplacementMode;
} }

View File

@ -16,6 +16,9 @@
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A DRM session. * A DRM session.
@ -23,6 +26,12 @@ import android.annotation.TargetApi;
@TargetApi(16) @TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> { 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. * 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}, * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
*/ */
@State
int getState(); int getState();
/** /**

View File

@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
/** /**
* Releases a {@link DrmSession}. * Releases a {@link DrmSession}.
*/ */
void releaseSession(DrmSession drmSession); void releaseSession(DrmSession<T> drmSession);
} }

View File

@ -40,7 +40,7 @@ import java.util.UUID;
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}. * A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
*/ */
@TargetApi(18) @TargetApi(18)
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager, public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
DrmSession<T> { DrmSession<T> {
/** /**
@ -87,6 +87,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
private int openCount; private int openCount;
private boolean provisioningInProgress; private boolean provisioningInProgress;
@DrmSession.State
private int state; private int state;
private T mediaCrypto; private T mediaCrypto;
private Exception lastException; private Exception lastException;
@ -267,7 +268,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
} }
@Override @Override
public void releaseSession(DrmSession session) { public void releaseSession(DrmSession<T> session) {
if (--openCount != 0) { if (--openCount != 0) {
return; return;
} }
@ -291,6 +292,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
// DrmSession implementation. // DrmSession implementation.
@Override @Override
@DrmSession.State
public final int getState() { public final int getState() {
return state; return state;
} }

View File

@ -15,11 +15,21 @@
*/ */
package com.google.android.exoplayer2.drm; 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. * Thrown when the requested DRM scheme is not supported.
*/ */
public final class UnsupportedDrmException extends Exception { 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. * 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}. * Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/ */
@Reason
public final int reason; public final int reason;
/** /**
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/ */
public UnsupportedDrmException(int reason) { public UnsupportedDrmException(@Reason int reason) {
this.reason = 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 reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
* @param cause The cause of this exception. * @param cause The cause of this exception.
*/ */
public UnsupportedDrmException(int reason, Exception cause) { public UnsupportedDrmException(@Reason int reason, Exception cause) {
super(cause); super(cause);
this.reason = reason; this.reason = reason;
} }

View File

@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput {
throws IOException, InterruptedException { throws IOException, InterruptedException {
ensureSpaceForPeek(length); ensureSpaceForPeek(length);
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
peekBufferLength += length - bytesPeeked;
while (bytesPeeked < length) { while (bytesPeeked < length) {
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
allowEndOfInput); allowEndOfInput);
@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput {
} }
} }
peekBufferPosition += length; peekBufferPosition += length;
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
return true; return true;
} }

View File

@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput {
long offset = extrasHolder.offset; long offset = extrasHolder.offset;
// Read the signal byte. // Read the signal byte.
scratch.reset(1);
readData(offset, scratch.data, 1); readData(offset, scratch.data, 1);
offset++; offset++;
byte signalByte = scratch.data[0]; byte signalByte = scratch.data[0];
@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput {
// Read the subsample count, if present. // Read the subsample count, if present.
int subsampleCount; int subsampleCount;
if (subsampleEncryption) { if (subsampleEncryption) {
scratch.reset(2);
readData(offset, scratch.data, 2); readData(offset, scratch.data, 2);
offset += 2; offset += 2;
scratch.setPosition(0);
subsampleCount = scratch.readUnsignedShort(); subsampleCount = scratch.readUnsignedShort();
} else { } else {
subsampleCount = 1; subsampleCount = 1;
@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
if (subsampleEncryption) { if (subsampleEncryption) {
int subsampleDataLength = 6 * subsampleCount; int subsampleDataLength = 6 * subsampleCount;
ensureCapacity(scratch, subsampleDataLength); scratch.reset(subsampleDataLength);
readData(offset, scratch.data, subsampleDataLength); readData(offset, scratch.data, subsampleDataLength);
offset += subsampleDataLength; offset += subsampleDataLength;
scratch.setPosition(0); 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. // Called by the loading thread.
/** /**
@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
@Override @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()) { if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs); infoQueue.commitSampleTimestamp(timeUs);
return; return;
@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
} }
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
byte[] encryptionKey) { int size, byte[] encryptionKey) {
Assertions.checkState(!upstreamFormatRequired); Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs); commitSampleTimestamp(timeUs);
timesUs[relativeWriteIndex] = timeUs; timesUs[relativeWriteIndex] = timeUs;

View File

@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput {
} }
@Override @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. // Do nothing.
} }

View File

@ -72,6 +72,7 @@ public interface TrackOutput {
* whose metadata is being passed. * whose metadata is being passed.
* @param encryptionKey The encryption key associated with the sample. May be null. * @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);
} }

View File

@ -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_TRACK_POSITIONS = 0xB7;
private static final int ID_CUE_CLUSTER_POSITION = 0xF1; private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
private static final int ID_LANGUAGE = 0x22B59C; 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_NONE = 0;
private static final int LACING_XIPH = 1; private static final int LACING_XIPH = 1;
@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor {
private int[] blockLacingSampleSizes; private int[] blockLacingSampleSizes;
private int blockTrackNumber; private int blockTrackNumber;
private int blockTrackNumberLength; private int blockTrackNumberLength;
@C.BufferFlags
private int blockFlags; private int blockFlags;
// Sample reading state. // Sample reading state.
@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_CUE_POINT: case ID_CUE_POINT:
case ID_CUE_TRACK_POSITIONS: case ID_CUE_TRACK_POSITIONS:
case ID_BLOCK_GROUP: case ID_BLOCK_GROUP:
case ID_PROJECTION:
return EbmlReader.TYPE_MASTER; return EbmlReader.TYPE_MASTER;
case ID_EBML_READ_VERSION: case ID_EBML_READ_VERSION:
case ID_DOC_TYPE_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_TIME:
case ID_CUE_CLUSTER_POSITION: case ID_CUE_CLUSTER_POSITION:
case ID_REFERENCE_BLOCK: case ID_REFERENCE_BLOCK:
case ID_STEREO_MODE:
return EbmlReader.TYPE_UNSIGNED_INT; return EbmlReader.TYPE_UNSIGNED_INT;
case ID_DOC_TYPE: case ID_DOC_TYPE:
case ID_CODEC_ID: case ID_CODEC_ID:
@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_SIMPLE_BLOCK: case ID_SIMPLE_BLOCK:
case ID_BLOCK: case ID_BLOCK:
case ID_CODEC_PRIVATE: case ID_CODEC_PRIVATE:
case ID_PROJECTION_PRIVATE:
return EbmlReader.TYPE_BINARY; return EbmlReader.TYPE_BINARY;
case ID_DURATION: case ID_DURATION:
case ID_SAMPLING_FREQUENCY: case ID_SAMPLING_FREQUENCY:
@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor {
case ID_BLOCK_DURATION: case ID_BLOCK_DURATION:
blockDurationUs = scaleTimecodeToUs(value); blockDurationUs = scaleTimecodeToUs(value);
return; 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: default:
return; return;
} }
@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor {
currentTrack.codecPrivate = new byte[contentSize]; currentTrack.codecPrivate = new byte[contentSize];
input.readFully(currentTrack.codecPrivate, 0, contentSize); input.readFully(currentTrack.codecPrivate, 0, contentSize);
return; return;
case ID_PROJECTION_PRIVATE:
currentTrack.projectionData = new byte[contentSize];
input.readFully(currentTrack.projectionData, 0, contentSize);
return;
case ID_CONTENT_COMPRESSION_SETTINGS: case ID_CONTENT_COMPRESSION_SETTINGS:
// This extractor only supports header stripping, so the payload is the stripped bytes. // This extractor only supports header stripping, so the payload is the stripped bytes.
currentTrack.sampleStrippedBytes = new byte[contentSize]; currentTrack.sampleStrippedBytes = new byte[contentSize];
@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor {
samplePartitionCountRead = true; samplePartitionCountRead = true;
} }
int samplePartitionDataSize = samplePartitionCount * 4; int samplePartitionDataSize = samplePartitionCount * 4;
if (scratch.limit() < samplePartitionDataSize) { scratch.reset(samplePartitionDataSize);
scratch.reset(new byte[samplePartitionDataSize], samplePartitionDataSize);
}
input.readFully(scratch.data, 0, samplePartitionDataSize); input.readFully(scratch.data, 0, samplePartitionDataSize);
sampleBytesRead += samplePartitionDataSize; sampleBytesRead += samplePartitionDataSize;
scratch.setPosition(0);
scratch.setLimit(samplePartitionDataSize);
short subsampleCount = (short) (1 + (samplePartitionCount / 2)); short subsampleCount = (short) (1 + (samplePartitionCount / 2));
int subsampleDataSize = 2 + 6 * subsampleCount; int subsampleDataSize = 2 + 6 * subsampleCount;
if (encryptionSubsampleDataBuffer == null if (encryptionSubsampleDataBuffer == null
@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor {
public int displayWidth = Format.NO_VALUE; public int displayWidth = Format.NO_VALUE;
public int displayHeight = Format.NO_VALUE; public int displayHeight = Format.NO_VALUE;
public int displayUnit = DISPLAY_UNIT_PIXELS; 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. // Audio elements. Initially set to their default values.
public int channelCount = 1; public int channelCount = 1;
@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor {
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
String mimeType; String mimeType;
int maxInputSize = Format.NO_VALUE; int maxInputSize = Format.NO_VALUE;
int pcmEncoding = Format.NO_VALUE; @C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
switch (codecId) { switch (codecId) {
case CODEC_ID_VP8: case CODEC_ID_VP8:
@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor {
} }
Format format; Format format;
int selectionFlags = 0; @C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? Format.SELECTION_FLAG_DEFAULT : 0; selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
selectionFlags |= flagForced ? Format.SELECTION_FLAG_FORCED : 0; selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them // TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats. // into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) { if (MimeTypes.isAudio(mimeType)) {
@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor {
} }
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, 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)) { } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, selectionFlags, language, drmInitData); Format.NO_VALUE, selectionFlags, language, drmInitData);

View File

@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor {
@Override @Override
public int read(ExtractorInput input, PositionHolder seekPosition) public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) { if (synchronizedHeaderData == 0) {
return RESULT_END_OF_INPUT; try {
synchronize(input, false);
} catch (EOFException e) {
return RESULT_END_OF_INPUT;
}
} }
if (seeker == null) { if (seeker == null) {
seeker = setupSeeker(input); seeker = setupSeeker(input);
@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor {
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) { if (sampleBytesRemaining == 0) {
if (!maybeResynchronize(extractorInput)) { extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
return RESULT_END_OF_INPUT; 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) { if (basisTimeUs == C.TIME_UNSET) {
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor {
return RESULT_CONTINUE; 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) private boolean synchronize(ExtractorInput input, boolean sniffing)
throws IOException, InterruptedException { throws IOException, InterruptedException {
int searched = 0;
int validFrameCount = 0; int validFrameCount = 0;
int candidateSynchronizedHeaderData = 0; int candidateSynchronizedHeaderData = 0;
int peekedId3Bytes = 0; int peekedId3Bytes = 0;
int searchedBytes = 0;
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition(); input.resetPeekPosition();
if (input.getPosition() == 0) { if (input.getPosition() == 0) {
Id3Util.parseId3(input, gaplessInfoHolder); Id3Util.parseId3(input, gaplessInfoHolder);
@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor {
} }
} }
while (true) { while (true) {
if (sniffing && searched == MAX_SNIFF_BYTES) { if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
return false; // We reached the end of the stream but found at least one valid frame.
} break;
if (!sniffing && searched == MAX_SYNC_BYTES) {
throw new ParserException("Searched too many bytes.");
}
if (!input.peekFully(scratch.data, 0, 4, true)) {
return false;
} }
scratch.setPosition(0); scratch.setPosition(0);
int headerData = scratch.readInt(); int headerData = scratch.readInt();
@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor {
if ((candidateSynchronizedHeaderData != 0 if ((candidateSynchronizedHeaderData != 0
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { || (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; validFrameCount = 0;
candidateSynchronizedHeaderData = 0; candidateSynchronizedHeaderData = 0;
searched++;
if (sniffing) { if (sniffing) {
input.resetPeekPosition(); input.resetPeekPosition();
input.advancePeekPosition(peekedId3Bytes + searched); input.advancePeekPosition(peekedId3Bytes + searchedBytes);
} else { } else {
input.skipFully(1); input.skipFully(1);
} }
} else { } else {
// The header is valid and matches the candidate header. // The header matches the candidate header and/or is valid.
validFrameCount++; validFrameCount++;
if (validFrameCount == 1) { if (validFrameCount == 1) {
MpegAudioHeader.populateHeader(headerData, synchronizedHeader); MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor {
} }
// Prepare to read the synchronized frame. // Prepare to read the synchronized frame.
if (sniffing) { if (sniffing) {
input.skipFully(peekedId3Bytes + searched); input.skipFully(peekedId3Bytes + searchedBytes);
} else { } else {
input.resetPeekPosition(); input.resetPeekPosition();
} }
@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor {
long position = input.getPosition(); long position = input.getPosition();
long length = input.getLength(); long length = input.getLength();
int headerData = 0;
Seeker seeker = null;
// Check if there is a Xing header. // Check if there is a Xing header.
int xingBase = (synchronizedHeader.version & 1) != 0 int xingBase = (synchronizedHeader.version & 1) != 0
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
frame.setPosition(xingBase); if (frame.limit() >= xingBase + 4) {
int headerData = frame.readInt(); frame.setPosition(xingBase);
Seeker seeker = null; headerData = frame.readInt();
}
if (headerData == XING_HEADER || headerData == INFO_HEADER) { if (headerData == XING_HEADER || headerData == INFO_HEADER) {
seeker = XingSeeker.create(synchronizedHeader, frame, position, length); seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor {
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
} }
input.skipFully(synchronizedHeader.frameSize); input.skipFully(synchronizedHeader.frameSize);
} else { } else if (frame.limit() >= 40) {
// Check if there is a VBRI header. // Check if there is a VBRI header.
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
headerData = frame.readInt(); headerData = frame.readInt();

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
@ -38,6 +39,8 @@ import java.util.List;
*/ */
/* package */ final class AtomParsers { /* package */ final class AtomParsers {
private static final String TAG = "AtomParsers";
private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
private static final int TYPE_soun = Util.getIntegerCodeForString("soun"); private static final int TYPE_soun = Util.getIntegerCodeForString("soun");
private static final int TYPE_text = Util.getIntegerCodeForString("text"); private static final int TYPE_text = Util.getIntegerCodeForString("text");
@ -248,11 +251,16 @@ import java.util.List;
remainingTimestampOffsetChanges--; remainingTimestampOffsetChanges--;
} }
// Check all the expected samples have been seen. // If the stbl's child boxes are not consistent the container is malformed, but the stream may
Assertions.checkArgument(remainingSynchronizationSamples == 0); // still be playable.
Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0); if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0
Assertions.checkArgument(remainingSamplesInChunk == 0); || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) {
Assertions.checkArgument(remainingTimestampDeltaChanges == 0); Log.w(TAG, "Inconsistent stbl box for track " + track.id
+ ": remainingSynchronizationSamples " + remainingSynchronizationSamples
+ ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta
+ ", remainingSamplesInChunk " + remainingSamplesInChunk
+ ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges);
}
} else { } else {
long[] chunkOffsetsBytes = new long[chunkIterator.length]; long[] chunkOffsetsBytes = new long[chunkIterator.length];
int[] chunkSampleCounts = new int[chunkIterator.length]; int[] chunkSampleCounts = new int[chunkIterator.length];
@ -636,7 +644,7 @@ import java.util.List;
0 /* subsample timing is absolute */); 0 /* subsample timing is absolute */);
} else if (childAtomType == Atom.TYPE_c608) { } else if (childAtomType == Atom.TYPE_c608) {
out.format = Format.createTextSampleFormat(Integer.toString(trackId), 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; out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
@ -665,6 +673,7 @@ import java.util.List;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
String mimeType = null; String mimeType = null;
byte[] projectionData = null; byte[] projectionData = null;
@C.StereoMode
int stereoMode = Format.NO_VALUE; int stereoMode = Format.NO_VALUE;
while (childPosition - position < size) { while (childPosition - position < size) {
parent.setPosition(childPosition); parent.setPosition(childPosition);
@ -889,7 +898,7 @@ import java.util.List;
if (out.format == null && mimeType != null) { if (out.format == null && mimeType != null) {
// TODO: Determine the correct PCM encoding. // TODO: Determine the correct PCM encoding.
int pcmEncoding = @C.PcmEncoding int pcmEncoding =
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE; MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
@ -1169,6 +1178,7 @@ import java.util.List;
public Format format; public Format format;
public int nalUnitLengthFieldLength; public int nalUnitLengthFieldLength;
@Track.Transformation
public int requiredSampleTransformation; public int requiredSampleTransformation;
public StsdData(int numberOfEntries) { public StsdData(int numberOfEntries) {

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; 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.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -64,6 +67,13 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final String TAG = "FragmentedMp4Extractor"; private static final String TAG = "FragmentedMp4Extractor";
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); 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. * 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 * 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. * 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; public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
/** /**
* Flag to ignore any tfdt boxes in the stream. * Flag to ignore any tfdt boxes in the stream.
*/ */
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2; 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 * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
* container. * container.
@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int STATE_READING_SAMPLE_CONTINUE = 4; private static final int STATE_READING_SAMPLE_CONTINUE = 4;
// Workarounds. // Workarounds.
@Flags
private final int flags; private final int flags;
private final Track sideloadedTrack; 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); 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 * @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data. * 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.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); 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, 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(); int moofContainerChildrenSize = moof.containerChildren.size();
for (int i = 0; i < moofContainerChildrenSize; i++) { for (int i = 0; i < moofContainerChildrenSize; i++) {
Atom.ContainerAtom child = moof.containerChildren.get(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). * Parses a traf atom (defined in 14496-12).
*/ */
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, 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); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
if (trackBundle == null) { if (trackBundle == null) {
@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
int flags) { @Flags int flags) {
int trunCount = 0; int trunCount = 0;
int totalSampleCount = 0; int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren; List<LeafAtom> leafChildren = traf.leafChildren;
@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param trun The trun atom to decode. * @param trun The trun atom to decode.
* @return The starting position of samples for the next run. * @return The starting position of samples for the next run.
*/ */
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags, private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
ParsableByteArray trun, int trackRunStart) { @Flags int flags, ParsableByteArray trun, int trackRunStart) {
trun.setPosition(Atom.HEADER_SIZE); trun.setPosition(Atom.HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int atomFlags = Atom.parseFullAtomFlags(fullAtom); int atomFlags = Atom.parseFullAtomFlags(fullAtom);
@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; 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); | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
byte[] encryptionKey = null; byte[] encryptionKey = null;

View File

@ -100,13 +100,14 @@ import java.io.IOException;
while (bytesSearched < bytesToSearch) { while (bytesSearched < bytesToSearch) {
// Read an atom header. // Read an atom header.
int headerSize = Atom.HEADER_SIZE; int headerSize = Atom.HEADER_SIZE;
buffer.reset(headerSize);
input.peekFully(buffer.data, 0, headerSize); input.peekFully(buffer.data, 0, headerSize);
buffer.setPosition(0);
long atomSize = buffer.readUnsignedInt(); long atomSize = buffer.readUnsignedInt();
int atomType = buffer.readInt(); int atomType = buffer.readInt();
if (atomSize == Atom.LONG_SIZE_PREFIX) { if (atomSize == Atom.LONG_SIZE_PREFIX) {
headerSize = Atom.LONG_HEADER_SIZE; headerSize = Atom.LONG_HEADER_SIZE;
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.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(); atomSize = buffer.readUnsignedLongToLong();
} }
@ -139,9 +140,7 @@ import java.io.IOException;
if (atomDataSize < 8) { if (atomDataSize < 8) {
return false; return false;
} }
if (buffer.capacity() < atomDataSize) { buffer.reset(atomDataSize);
buffer.reset(new byte[atomDataSize], atomDataSize);
}
input.peekFully(buffer.data, 0, atomDataSize); input.peekFully(buffer.data, 0, atomDataSize);
int brandsCount = atomDataSize / 4; int brandsCount = atomDataSize / 4;
for (int i = 0; i < brandsCount; i++) { for (int i = 0; i < brandsCount; i++) {

View File

@ -15,14 +15,23 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Encapsulates information describing an MP4 track. * Encapsulates information describing an MP4 track.
*/ */
public final class 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. * 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 * One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
* sample. * sample.
*/ */
@Transformation
public final int sampleTransformation; public final int sampleTransformation;
/** /**
@ -90,7 +100,7 @@ public final class Track {
public final int nalUnitLengthFieldLength; public final int nalUnitLengthFieldLength;
public Track(int id, int type, long timescale, long movieTimescale, long durationUs, 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, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
long[] editListDurations, long[] editListMediaTimes) { long[] editListDurations, long[] editListMediaTimes) {
this.id = id; this.id = id;

View File

@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util;
*/ */
public final int[] flags; 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(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length);

View File

@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; 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 { public final class RawCcExtractor implements Extractor {
@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor {
trackOutput = extractorOutput.track(0); trackOutput = extractorOutput.track(0);
extractorOutput.endTracks(); 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)); null, Format.NO_VALUE, 0, null, null));
} }
@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor {
dataScratch.reset(); dataScratch.reset();
input.readFully(dataScratch.data, 0, 3); input.readFully(dataScratch.data, 0, 3);
// only accept EIA-608 packets which have validity (6th bit) == 1 and trackOutput.sampleData(dataScratch, 3);
// type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100 sampleBytesWritten += 3;
int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07;
if (ccValidityAndType == 0x04) {
trackOutput.sampleData(dataScratch, 2);
sampleBytesWritten += 2;
}
} }
if (sampleBytesWritten > 0) { if (sampleBytesWritten > 0) {

View File

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

View File

@ -15,13 +15,60 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; 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.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
/** /**
* Extracts individual samples from an elementary media stream, preserving original order. * 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; protected final TrackOutput output;

View File

@ -128,7 +128,7 @@ import java.util.Collections;
if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) {
int bytesWrittenPastStartCode = limit - startCodeOffset; int bytesWrittenPastStartCode = limit - startCodeOffset;
if (foundFirstFrameInGroup) { 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; int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode;
output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null);
isKeyframe = false; isKeyframe = false;
@ -136,7 +136,7 @@ import java.util.Collections;
if (startCodeValue == START_GROUP) { if (startCodeValue == START_GROUP) {
foundFirstFrameInGroup = false; foundFirstFrameInGroup = false;
isKeyframe = true; isKeyframe = true;
} else /* startCode == START_PICTURE */ { } else /* startCodeValue == START_PICTURE */ {
frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs);
framePosition = totalBytesWritten - bytesWrittenPastStartCode; framePosition = totalBytesWritten - bytesWrittenPastStartCode;
pesPtsUsAvailable = false; pesPtsUsAvailable = false;

View File

@ -57,7 +57,7 @@ import java.util.List;
/** /**
* @param output A {@link TrackOutput} to which H.264 samples should be written. * @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 * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames). * synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on * @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) { 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); int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null); output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
} }

View File

@ -64,7 +64,7 @@ import java.util.Collections;
/** /**
* @param output A {@link TrackOutput} to which H.265 samples should be written. * @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) { public H265Reader(TrackOutput output, SeiReader seiReader) {
super(output); super(output);
@ -471,7 +471,7 @@ import java.util.Collections;
} }
private void outputSample(int offset) { 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); int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null); output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
} }

View File

@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor {
input.peekFully(psPacketBuffer.data, 0, 10); input.peekFully(psPacketBuffer.data, 0, 10);
// We only care about the pack_stuffing_length in here, skip the first 77 bits. // We only care about the pack_stuffing_length in here, skip the first 77 bits.
psPacketBuffer.setPosition(0); psPacketBuffer.setPosition(9);
psPacketBuffer.skipBytes(9);
// Last 3 bits is the length. // Last 3 bits is the length.
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07; 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); input.peekFully(psPacketBuffer.data, 0, 2);
psPacketBuffer.setPosition(0); psPacketBuffer.setPosition(0);
int payloadLength = psPacketBuffer.readUnsignedShort(); int payloadLength = psPacketBuffer.readUnsignedShort();
@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor {
// Just skip this data. // Just skip this data.
input.skipFully(pesLength); input.skipFully(pesLength);
} else { } else {
if (psPacketBuffer.capacity() < pesLength) { psPacketBuffer.reset(pesLength);
// Reallocate for this and future packets.
psPacketBuffer.reset(new byte[pesLength], pesLength);
}
// Read the whole packet and the header for consumption. // Read the whole packet and the header for consumption.
input.readFully(psPacketBuffer.data, 0, pesLength); input.readFully(psPacketBuffer.data, 0, pesLength);
psPacketBuffer.setPosition(6); psPacketBuffer.setPosition(6);
psPacketBuffer.setLimit(pesLength);
payloadReader.consume(psPacketBuffer); payloadReader.consume(psPacketBuffer);
psPacketBuffer.setLimit(psPacketBuffer.capacity()); psPacketBuffer.setLimit(psPacketBuffer.capacity());
} }

View File

@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.TrackOutput; 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.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; 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 { /* package */ final class SeiReader {
@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
public SeiReader(TrackOutput output) { public SeiReader(TrackOutput output) {
this.output = 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)); Format.NO_VALUE, 0, null, null));
} }
@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
payloadSize += b; payloadSize += b;
} while (b == 0xFF); } while (b == 0xFF);
// Process the payload. // 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) // Ignore country_code (1) + provider_code (2) + user_identifier (4)
// + user_data_type_code (1). // + user_data_type_code (1).
seiBuffer.skipBytes(8); seiBuffer.skipBytes(8);
@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
seiBuffer.skipBytes(1); seiBuffer.skipBytes(1);
int sampleBytes = 0; int sampleBytes = 0;
for (int i = 0; i < ccCount; i++) { for (int i = 0; i < ccCount; i++) {
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07; int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07;
// Check that validity == 1 and type == 0. // Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1).
if (ccValidityAndType != 0x04) { if (ccValidityAndType != 0x04) {
seiBuffer.skipBytes(2); seiBuffer.skipBytes(3);
} else { } else {
sampleBytes += 2; sampleBytes += 3;
output.sampleData(seiBuffer, 2); output.sampleData(seiBuffer, 3);
} }
} }
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null); output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);

View File

@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import com.google.android.exoplayer2.C; 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.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* Facilitates the extraction of data from the MPEG-2 TS container format. * 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 String TAG = "TsExtractor";
private static final int TS_PACKET_SIZE = 188; 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_SYNC_BYTE = 0x47; // First byte of each TS packet.
private static final int TS_PAT_PID = 0; private static final int TS_PAT_PID = 0;
private static final int TS_STREAM_TYPE_MPA = 0x03; public static final int TS_STREAM_TYPE_MPA = 0x03;
private static final int TS_STREAM_TYPE_MPA_LSF = 0x04; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
private static final int TS_STREAM_TYPE_AAC = 0x0F; public static final int TS_STREAM_TYPE_AAC = 0x0F;
private static final int TS_STREAM_TYPE_AC3 = 0x81; public static final int TS_STREAM_TYPE_AC3 = 0x81;
private static final int TS_STREAM_TYPE_DTS = 0x8A; public static final int TS_STREAM_TYPE_DTS = 0x8A;
private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
private static final int TS_STREAM_TYPE_E_AC3 = 0x87; public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
private static final int TS_STREAM_TYPE_H262 = 0x02; public static final int TS_STREAM_TYPE_H262 = 0x02;
private static final int TS_STREAM_TYPE_H264 = 0x1B; public static final int TS_STREAM_TYPE_H264 = 0x1B;
private static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_H265 = 0x24;
private static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); 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 static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private final int workaroundFlags;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters; private final SparseIntArray continuityCounters;
private final ElementaryStreamReader.Factory streamReaderFactory;
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid /* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
/* package */ final SparseBooleanArray trackIds;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private ExtractorOutput output; private ExtractorOutput output;
private int nextEmbeddedTrackId;
/* package */ Id3Reader id3Reader;
public TsExtractor() { public TsExtractor() {
this(new TimestampAdjuster(0)); this(new TimestampAdjuster(0));
} }
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) { 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.timestampAdjuster = timestampAdjuster;
this.workaroundFlags = workaroundFlags; this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
tsScratch = new ParsableBitArray(new byte[3]); tsScratch = new ParsableBitArray(new byte[3]);
tsPayloadReaders = new SparseArray<>(); tsPayloadReaders = new SparseArray<>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader()); tsPayloadReaders.put(TS_PAT_PID, new PatReader());
trackIds = new SparseBooleanArray();
nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
continuityCounters = new SparseIntArray(); continuityCounters = new SparseIntArray();
} }
@ -416,12 +412,6 @@ public final class TsExtractor implements Extractor {
// Skip the descriptors. // Skip the descriptors.
sectionData.skipBytes(programInfoLength); 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 */ int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
- programInfoLength - 4 /* CRC length */; - programInfoLength - 4 /* CRC length */;
while (remainingEntriesLength > 0) { while (remainingEntriesLength > 0) {
@ -431,63 +421,15 @@ public final class TsExtractor implements Extractor {
int elementaryPid = pmtScratch.readBits(13); int elementaryPid = pmtScratch.readBits(13);
pmtScratch.skipBits(4); // reserved pmtScratch.skipBits(4); // reserved
int esInfoLength = pmtScratch.readBits(12); // ES_info_length. int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
EsInfo esInfo = readEsInfo(sectionData, esInfoLength); ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
if (streamType == 0x06) { if (streamType == 0x06) {
streamType = esInfo.streamType; streamType = esInfo.streamType;
} }
remainingEntriesLength -= esInfoLength + 5; remainingEntriesLength -= esInfoLength + 5;
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid; ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid,
if (trackIds.get(trackId)) { streamType, esInfo, output);
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;
}
if (pesPayloadReader != null) { if (pesPayloadReader != null) {
trackIds.put(trackId, true);
tsPayloadReaders.put(elementaryPid, tsPayloadReaders.put(elementaryPid,
new PesReader(pesPayloadReader, timestampAdjuster)); 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 * Returns the stream info read from the available descriptors. Sets {@code data}'s position to
* descriptors are present. Sets {@code data}'s position to the end of the descriptors. * the end of the descriptors.
* *
* @param data A buffer with its position set to the start of the first descriptor. * @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}. * @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 * @return The stream info read from the available descriptors.
* descriptors are present.
*/ */
private EsInfo readEsInfo(ParsableByteArray data, int length) { private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) {
int descriptorsEndPosition = data.getPosition() + length; int descriptorsStartPosition = data.getPosition();
int descriptorsEndPosition = descriptorsStartPosition + length;
int streamType = -1; int streamType = -1;
int audioType = -1;
String language = null; String language = null;
while (data.getPosition() < descriptorsEndPosition) { while (data.getPosition() < descriptorsEndPosition) {
int descriptorTag = data.readUnsignedByte(); int descriptorTag = data.readUnsignedByte();
@ -531,27 +472,14 @@ public final class TsExtractor implements Extractor {
streamType = TS_STREAM_TYPE_DTS; streamType = TS_STREAM_TYPE_DTS;
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
language = new String(data.data, data.getPosition(), 3).trim(); 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. // Skip unused bytes of current descriptor.
data.skipBytes(positionOfNextDescriptor - data.getPosition()); data.skipBytes(positionOfNextDescriptor - data.getPosition());
} }
data.setPosition(descriptorsEndPosition); data.setPosition(descriptorsEndPosition);
return new EsInfo(streamType, audioType, language); return new ElementaryStreamReader.EsInfo(streamType, language,
} Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
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;
}
} }
} }

View File

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C;
/** Bits per sample for the audio data. */ /** Bits per sample for the audio data. */
private final int bitsPerSample; private final int bitsPerSample;
/** The PCM encoding */ /** The PCM encoding */
@C.PcmEncoding
private final int encoding; private final int encoding;
/** Offset to the start of sample data. */ /** Offset to the start of sample data. */
@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C;
private long dataSize; private long dataSize;
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
int bitsPerSample, int encoding) { int bitsPerSample, @C.PcmEncoding int encoding) {
this.numChannels = numChannels; this.numChannels = numChannels;
this.sampleRateHz = sampleRateHz; this.sampleRateHz = sampleRateHz;
this.averageBytesPerSecond = averageBytesPerSecond; this.averageBytesPerSecond = averageBytesPerSecond;
@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C;
} }
/** Returns the PCM encoding. **/ /** Returns the PCM encoding. **/
@C.PcmEncoding
public int getEncoding() { public int getEncoding() {
return encoding; return encoding;
} }

View File

@ -87,7 +87,7 @@ import java.io.IOException;
+ blockAlignment); + blockAlignment);
} }
int encoding = Util.getPcmEncoding(bitsPerSample); @C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample);
if (encoding == C.ENCODING_INVALID) { if (encoding == C.ENCODING_INVALID) {
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample); Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
return null; return null;

View File

@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
MediaCrypto mediaCrypto = null; MediaCrypto mediaCrypto = null;
boolean drmSessionRequiresSecureDecoder = false; boolean drmSessionRequiresSecureDecoder = false;
if (drmSession != null) { if (drmSession != null) {
int drmSessionState = drmSession.getState(); @DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED } else if (drmSessionState == DrmSession.STATE_OPENED
@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (drmSession == null) { if (drmSession == null) {
return false; return false;
} }
int drmSessionState = drmSession.getState(); @DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} }

View File

@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; 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.Allocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
int sourceIndex = timeline.getSourceIndexForPeriod(index); int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, callback, MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator,
allocator, positionUs); positionUs);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
return mediaPeriod; return mediaPeriod;
} }

View File

@ -61,12 +61,15 @@ import java.util.Arrays;
private final Handler eventHandler; private final Handler eventHandler;
private final ExtractorMediaSource.EventListener eventListener; private final ExtractorMediaSource.EventListener eventListener;
private final MediaSource.Listener sourceListener; private final MediaSource.Listener sourceListener;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final Loader loader; private final Loader loader;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
private final ConditionVariable loadCondition; private final ConditionVariable loadCondition;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
private Callback callback;
private SeekMap seekMap; private SeekMap seekMap;
private boolean tracksBuilt; private boolean tracksBuilt;
private boolean prepared; private boolean prepared;
@ -85,6 +88,7 @@ import java.util.Arrays;
private int extractedSamplesCountAtStartOfLoad; private int extractedSamplesCountAtStartOfLoad;
private boolean loadingFinished; private boolean loadingFinished;
private boolean released;
/** /**
* @param uri The {@link Uri} of the media stream. * @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 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 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 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. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
*/ */
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener,
Callback callback, Allocator allocator) { Allocator allocator) {
this.uri = uri; this.uri = uri;
this.dataSource = dataSource; this.dataSource = dataSource;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.sourceListener = sourceListener; this.sourceListener = sourceListener;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
loader = new Loader("Loader:ExtractorMediaPeriod"); loader = new Loader("Loader:ExtractorMediaPeriod");
extractorHolder = new ExtractorHolder(extractors, this); extractorHolder = new ExtractorHolder(extractors, this);
loadCondition = new ConditionVariable(); 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; pendingResetPositionUs = C.TIME_UNSET;
sampleQueues = new DefaultTrackOutput[0]; sampleQueues = new DefaultTrackOutput[0];
length = C.LENGTH_UNSET; length = C.LENGTH_UNSET;
loadCondition.open();
startLoading();
} }
public void release() { public void release() {
@ -126,11 +141,20 @@ import java.util.Arrays;
@Override @Override
public void run() { public void run() {
extractorHolder.release(); extractorHolder.release();
for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable();
}
} }
}); });
for (DefaultTrackOutput sampleQueue : sampleQueues) { handler.removeCallbacksAndMessages(null);
sampleQueue.disable(); released = true;
} }
@Override
public void prepare(Callback callback) {
this.callback = callback;
loadCondition.open();
startLoading();
} }
@Override @Override
@ -330,7 +354,7 @@ import java.util.Arrays;
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
} }
// ExtractorOutput implementation. // ExtractorOutput implementation. Called by the loading thread.
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id) {
@ -344,26 +368,26 @@ import java.util.Arrays;
@Override @Override
public void endTracks() { public void endTracks() {
tracksBuilt = true; tracksBuilt = true;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
@Override @Override
public void seekMap(SeekMap seekMap) { public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap; this.seekMap = seekMap;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// UpstreamFormatChangedListener implementation // UpstreamFormatChangedListener implementation. Called by the loading thread.
@Override @Override
public void onUpstreamFormatChanged(Format format) { public void onUpstreamFormatChanged(Format format) {
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// Internal methods. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
if (prepared || seekMap == null || !tracksBuilt) { if (released || prepared || seekMap == null || !tracksBuilt) {
return; return;
} }
for (DefaultTrackOutput sampleQueue : sampleQueues) { for (DefaultTrackOutput sampleQueue : sampleQueues) {
@ -576,7 +600,7 @@ import java.util.Arrays;
if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) {
position = input.getPosition(); position = input.getPosition();
loadCondition.close(); loadCondition.close();
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); handler.post(onContinueLoadingRequestedRunnable);
} }
} }
} finally { } finally {

View File

@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; 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.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(),
extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener,
this, callback, allocator); this, allocator);
} }
@Override @Override

View File

@ -19,7 +19,6 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; 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.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) { return childSource.createPeriod(index % childPeriodCount, allocator, positionUs);
return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs);
} }
@Override @Override

View File

@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Called when preparation completes. * Called when preparation completes.
* <p> * <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 * for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
* called with the initial track selection. * 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 * Throws an error that's preventing the period from becoming prepared. Does nothing if no such
* error exists. * error exists.

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.Timeline; 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.Allocator;
import java.io.IOException; import java.io.IOException;
@ -54,18 +53,13 @@ public interface MediaSource {
/** /**
* Returns a {@link MediaPeriod} corresponding to the period at the specified index. * 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 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 allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The player's current playback position. * @param positionUs The player's current playback position.
* @return A new {@link MediaPeriod}. * @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. * Releases the period.

View File

@ -28,20 +28,27 @@ import java.util.IdentityHashMap;
public final MediaPeriod[] periods; public final MediaPeriod[] periods;
private final Callback callback;
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices; private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
private Callback callback;
private int pendingChildPrepareCount; private int pendingChildPrepareCount;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private MediaPeriod[] enabledPeriods; private MediaPeriod[] enabledPeriods;
private SequenceableLoader sequenceableLoader; private SequenceableLoader sequenceableLoader;
public MergingMediaPeriod(Callback callback, MediaPeriod... periods) { public MergingMediaPeriod(MediaPeriod... periods) {
this.periods = periods; this.periods = periods;
this.callback = callback;
streamPeriodIndices = new IdentityHashMap<>(); streamPeriodIndices = new IdentityHashMap<>();
}
@Override
public void prepare(Callback callback) {
this.callback = callback;
pendingChildPrepareCount = periods.length; pendingChildPrepareCount = periods.length;
for (MediaPeriod period : periods) {
period.prepare(this);
}
} }
@Override @Override

View File

@ -15,11 +15,12 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.Timeline; 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.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -36,11 +37,16 @@ public final class MergingMediaSource implements MediaSource {
*/ */
public static final class IllegalMergeException extends IOException { 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. * The merge failed because one of the sources being merged has a dynamic window.
*/ */
public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; public static final int REASON_WINDOWS_ARE_DYNAMIC = 0;
/** /**
* The merge failed because the sources have different period counts. * 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 * The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
* {@link #REASON_PERIOD_COUNT_MISMATCH}. * {@link #REASON_PERIOD_COUNT_MISMATCH}.
*/ */
@Reason
public final int reason; public final int reason;
/** /**
* @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and * @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
* {@link #REASON_PERIOD_COUNT_MISMATCH}. * {@link #REASON_PERIOD_COUNT_MISMATCH}.
*/ */
public IllegalMergeException(int reason) { public IllegalMergeException(@Reason int reason) {
this.reason = reason; this.reason = reason;
} }
@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; 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++) { for (int i = 0; i < periods.length; i++) {
periods[i] = mediaSources[i].createPeriod(index, mergingPeriod, allocator, positionUs); periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs);
Assertions.checkState(periods[i] != null, "Child source must not return null period");
} }
return mergingPeriod; return new MergingMediaPeriod(periods);
} }
@Override @Override

View File

@ -29,7 +29,7 @@ public interface SequenceableLoader {
/** /**
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method * 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); void onContinueLoadingRequested(T source);

View File

@ -78,6 +78,11 @@ import java.util.Arrays;
loader.release(); loader.release();
} }
@Override
public void prepare(Callback callback) {
callback.onPrepared(this);
}
@Override @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
loader.maybeThrowError(); loader.maybeThrowError();

View File

@ -19,7 +19,6 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline; 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.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
MediaPeriod mediaPeriod = new SingleSampleMediaPeriod(uri, dataSourceFactory, format, return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount,
minLoadableRetryCount, eventHandler, eventListener, eventSourceId); eventHandler, eventListener, eventSourceId);
callback.onPrepared(mediaPeriod);
return mediaPeriod;
} }
@Override @Override

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.chunk; package com.google.android.exoplayer2.source.chunk;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
} }
@Override @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); trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
} }

View File

@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
} }
@Override @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"); throw new IllegalStateException("Unexpected sample data in initialization chunk");
} }

View File

@ -48,10 +48,10 @@ import java.util.List;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final long elapsedRealtimeOffset; private final long elapsedRealtimeOffset;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams; private ChunkSampleStream<DashChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
private DashManifest manifest; private DashManifest manifest;
@ -61,7 +61,7 @@ import java.util.List;
public DashMediaPeriod(int id, DashManifest manifest, int index, public DashMediaPeriod(int id, DashManifest manifest, int index,
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
EventDispatcher eventDispatcher, long elapsedRealtimeOffset, EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
this.id = id; this.id = id;
this.manifest = manifest; this.manifest = manifest;
this.index = index; this.index = index;
@ -70,13 +70,11 @@ import java.util.List;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.elapsedRealtimeOffset = elapsedRealtimeOffset;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
period = manifest.getPeriod(index); period = manifest.getPeriod(index);
trackGroups = buildTrackGroups(period); trackGroups = buildTrackGroups(period);
callback.onPrepared(this);
} }
public void updateManifest(DashManifest manifest, int index) { 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 @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();

View File

@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; 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.MediaSource;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index, DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index,
chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader, chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader,
callback, allocator); allocator);
periodsById.put(mediaPeriod.id, mediaPeriod); periodsById.put(mediaPeriod.id, mediaPeriod);
return mediaPeriod; return mediaPeriod;
} }

View File

@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler
} else if (MimeTypes.isVideo(containerMimeType)) { } else if (MimeTypes.isVideo(containerMimeType)) {
return MimeTypes.getVideoMediaMimeType(codecs); return MimeTypes.getVideoMediaMimeType(codecs);
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
// We currently only support EIA-608 through RawCC // We currently only support CEA-608 through RawCC
if (codecs != null && codecs.contains("eia608")) { if (codecs != null
return MimeTypes.APPLICATION_EIA608; && (codecs.contains("eia608") || codecs.contains("cea608"))) {
return MimeTypes.APPLICATION_CEA608;
} }
return null; return null;
} else if (mimeTypeIsRawText(containerMimeType)) { } else if (mimeTypeIsRawText(containerMimeType)) {

View File

@ -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.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; 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.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
@ -355,20 +356,22 @@ import java.util.Locale;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs); startTimeUs);
// This flag ensures the change of pid between streams does not affect the sample queues. // 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; String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) { if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // 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 // exist. If we know from the codec attribute that they don't exist, then we can explicitly
// ignore them even if they're declared. // ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { 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))) { 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 { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor; extractor = previous.extractor;

View File

@ -50,11 +50,11 @@ import java.util.List;
/* package */ final class HlsMediaPeriod implements MediaPeriod, /* package */ final class HlsMediaPeriod implements MediaPeriod,
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback { Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback {
private final Uri manifestUri;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final MediaSource.Listener sourceListener; private final MediaSource.Listener sourceListener;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices; private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
@ -62,7 +62,9 @@ import java.util.List;
private final Handler continueLoadingHandler; private final Handler continueLoadingHandler;
private final Loader manifestFetcher; private final Loader manifestFetcher;
private final long preparePositionUs; private final long preparePositionUs;
private final Runnable continueLoadingRunnable;
private Callback callback;
private int pendingPrepareCount; private int pendingPrepareCount;
private HlsPlaylist playlist; private HlsPlaylist playlist;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
@ -72,17 +74,16 @@ import java.util.List;
private HlsSampleStreamWrapper[] sampleStreamWrappers; private HlsSampleStreamWrapper[] sampleStreamWrappers;
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
private Runnable continueLoadingRunnable;
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory, public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, int minLoadableRetryCount, EventDispatcher eventDispatcher,
MediaSource.Listener sourceListener, final Callback callback, Allocator allocator, MediaSource.Listener sourceListener, Allocator allocator,
long positionUs) { long positionUs) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.sourceListener = sourceListener; this.sourceListener = sourceListener;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
streamWrapperIndices = new IdentityHashMap<>(); streamWrapperIndices = new IdentityHashMap<>();
timestampAdjusterProvider = new TimestampAdjusterProvider(); timestampAdjusterProvider = new TimestampAdjusterProvider();
@ -96,11 +97,6 @@ import java.util.List;
callback.onContinueLoadingRequested(HlsMediaPeriod.this); 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() { 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 @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
if (sampleStreamWrappers == null) { if (sampleStreamWrappers == null) {
@ -239,13 +244,7 @@ import java.util.List;
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded()); loadDurationMs, loadable.bytesLoaded());
playlist = loadable.getResult(); playlist = loadable.getResult();
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers(); buildAndPrepareSampleStreamWrappers();
sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()];
sampleStreamWrapperList.toArray(sampleStreamWrappers);
pendingPrepareCount = sampleStreamWrappers.length;
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
sampleStreamWrapper.prepare();
}
} }
@Override @Override
@ -313,16 +312,16 @@ import java.util.List;
// Internal methods. // Internal methods.
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers() { private void buildAndPrepareSampleStreamWrappers() {
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
String baseUri = playlist.baseUri; String baseUri = playlist.baseUri;
if (playlist instanceof HlsMediaPlaylist) { if (playlist instanceof HlsMediaPlaylist) {
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] { HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)}; HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, sampleStreamWrappers = new HlsSampleStreamWrapper[] {
null, null)); buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)};
return sampleStreamWrappers; pendingPrepareCount = 1;
sampleStreamWrappers[0].prepare();
return;
} }
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
@ -351,32 +350,37 @@ import java.util.List;
} else { } else {
// Leave the enabled variants unchanged. They're likely either all video or all audio. // 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()) { if (!selectedVariants.isEmpty()) {
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()];
selectedVariants.toArray(variants); selectedVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT,
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat)); baseUri, variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat);
sampleStreamWrapper.prepare();
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
} }
// Build the audio stream wrapper if applicable. // Build audio stream wrappers.
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios; for (int i = 0; i < audioVariants.size(); i++) {
if (!audioVariants.isEmpty()) { HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO,
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[audioVariants.size()]; baseUri, new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null);
audioVariants.toArray(variants); sampleStreamWrapper.prepare();
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null, sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
null));
} }
// Build the text stream wrapper if applicable. // Build subtitle stream wrappers.
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles; for (int i = 0; i < subtitleVariants.size(); i++) {
if (!subtitleVariants.isEmpty()) { HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i);
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[subtitleVariants.size()]; HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT,
subtitleVariants.toArray(variants); baseUri, new HlsMasterPlaylist.HlsUrl[] {url}, null, null);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null, sampleStreamWrapper.prepareSingleTrack(url.format);
null)); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
} }
return sampleStreamWrappers;
} }
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri, private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,

View File

@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; 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.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount, return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount,
eventDispatcher, sourceListener, callback, allocator, positionUs); eventDispatcher, sourceListener, allocator, positionUs);
} }
@Override @Override

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.os.Handler;
import android.text.TextUtils;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
@ -80,13 +82,15 @@ import java.util.LinkedList;
private final HlsChunkSource.HlsChunkHolder nextChunkHolder; private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
private final SparseArray<DefaultTrackOutput> sampleQueues; private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks; private final LinkedList<HlsMediaChunk> mediaChunks;
private final Runnable maybeFinishPrepareRunnable;
private final Handler handler;
private volatile boolean sampleQueuesBuilt; private boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
private int enabledTrackCount; private int enabledTrackCount;
private Format downstreamTrackFormat; private Format downstreamTrackFormat;
private int upstreamChunkUid; private int upstreamChunkUid;
private boolean released;
// Tracks are complicated in HLS. See documentation of buildTracks for details. // Tracks are complicated in HLS. See documentation of buildTracks for details.
// Indexed by track (as exposed by this source). // Indexed by track (as exposed by this source).
@ -129,6 +133,13 @@ import java.util.LinkedList;
nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
sampleQueues = new SparseArray<>(); sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>(); mediaChunks = new LinkedList<>();
maybeFinishPrepareRunnable = new Runnable() {
@Override
public void run() {
maybeFinishPrepare();
}
};
handler = new Handler();
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
} }
@ -137,6 +148,15 @@ import java.util.LinkedList;
continueLoading(lastSeekPositionUs); 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 { public void maybeThrowPrepareError() throws IOException {
maybeThrowError(); maybeThrowError();
} }
@ -245,6 +265,8 @@ import java.util.LinkedList;
sampleQueues.valueAt(i).disable(); sampleQueues.valueAt(i).disable();
} }
loader.release(); loader.release();
handler.removeCallbacksAndMessages(null);
released = true;
} }
public long getLargestQueuedTimestampUs() { public long getLargestQueuedTimestampUs() {
@ -454,7 +476,7 @@ import java.util.LinkedList;
@Override @Override
public void endTracks() { public void endTracks() {
sampleQueuesBuilt = true; sampleQueuesBuilt = true;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
@Override @Override
@ -462,17 +484,17 @@ import java.util.LinkedList;
// Do nothing. // Do nothing.
} }
// UpstreamFormatChangedListener implementation. // UpstreamFormatChangedListener implementation. Called by the loading thread.
@Override @Override
public void onUpstreamFormatChanged(Format format) { public void onUpstreamFormatChanged(Format format) {
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// Internal methods. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
if (prepared || !sampleQueuesBuilt) { if (released || prepared || !sampleQueuesBuilt) {
return; return;
} }
int sampleQueueCount = sampleQueues.size(); int sampleQueueCount = sampleQueues.size();
@ -558,7 +580,7 @@ import java.util.LinkedList;
if (i == primaryExtractorTrackIndex) { if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount]; Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) { 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); trackGroups[i] = new TrackGroup(formats);
primaryTrackGroupIndex = i; primaryTrackGroupIndex = i;
@ -567,11 +589,11 @@ import java.util.LinkedList;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
trackFormat = muxedAudioFormat; trackFormat = muxedAudioFormat;
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) { } else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) {
trackFormat = muxedCaptionFormat; trackFormat = muxedCaptionFormat;
} }
} }
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat)); trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
} }
} }
this.trackGroups = new TrackGroupArray(trackGroups); 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 * Derives a track format corresponding to a given container format, by combining it with sample
* level information obtained from a second sample format. * 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. * @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) { if (containerFormat == null) {
return sampleFormat; 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.width, containerFormat.height, containerFormat.selectionFlags,
containerFormat.language); containerFormat.language);
} }
@ -614,4 +643,29 @@ import java.util.LinkedList;
return pendingResetPositionUs != C.TIME_UNSET; 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;
}
} }

View File

@ -15,18 +15,29 @@
*/ */
package com.google.android.exoplayer2.source.hls.playlist; 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. * Represents an HLS playlist.
*/ */
public abstract class HlsPlaylist { 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_MASTER = 0;
public static final int TYPE_MEDIA = 1; public static final int TYPE_MEDIA = 1;
public final String baseUri; public final String baseUri;
@Type
public final int type; public final int type;
protected HlsPlaylist(String baseUri, int type) { protected HlsPlaylist(String baseUri, @Type int type) {
this.baseUri = baseUri; this.baseUri = baseUri;
this.type = type; this.type = type;
} }

View File

@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String BOOLEAN_TRUE = "YES"; private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO"; private static final String BOOLEAN_FALSE = "NO";
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\""); 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_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
@ -138,7 +137,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
while (iterator.hasNext()) { while (iterator.hasNext()) {
line = iterator.next(); line = iterator.next();
if (line.startsWith(TAG_MEDIA)) { if (line.startsWith(TAG_MEDIA)) {
int selectionFlags = parseSelectionFlags(line); @C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
String uri = parseOptionalStringAttr(line, REGEX_URI); String uri = parseOptionalStringAttr(line, REGEX_URI);
String name = parseStringAttr(line, REGEX_NAME); String name = parseStringAttr(line, REGEX_NAME);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
@ -162,7 +161,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
case TYPE_CLOSED_CAPTIONS: case TYPE_CLOSED_CAPTIONS:
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) { if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
muxedCaptionFormat = Format.createTextContainerFormat(name, 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); selectionFlags, language);
} }
break; break;
@ -200,11 +199,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
muxedCaptionFormat); muxedCaptionFormat);
} }
@C.SelectionFlags
private static int parseSelectionFlags(String line) { private static int parseSelectionFlags(String line) {
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0) return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0)
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0) | (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0)
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT | (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0);
: 0);
} }
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)

View File

@ -46,23 +46,22 @@ import java.util.ArrayList;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private final TrackEncryptionBox[] trackEncryptionBoxes; private final TrackEncryptionBox[] trackEncryptionBoxes;
private Callback callback;
private SsManifest manifest; private SsManifest manifest;
private ChunkSampleStream<SsChunkSource>[] sampleStreams; private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, int minLoadableRetryCount, EventDispatcher eventDispatcher,
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
trackGroups = buildTrackGroups(manifest); trackGroups = buildTrackGroups(manifest);
@ -77,7 +76,6 @@ import java.util.ArrayList;
this.manifest = manifest; this.manifest = manifest;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
callback.onPrepared(this);
} }
public void updateManifest(SsManifest manifest) { 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 @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();

View File

@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; 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.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource,
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
eventDispatcher, manifestLoader, callback, allocator); eventDispatcher, manifestLoader, allocator);
mediaPeriods.add(period); mediaPeriods.add(period);
return period; return period;
} }

View File

@ -18,35 +18,41 @@ package com.google.android.exoplayer2.text;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle; import android.view.accessibility.CaptioningManager.CaptionStyle;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A compatibility wrapper for {@link CaptionStyle}. * A compatibility wrapper for {@link CaptionStyle}.
*/ */
public final class CaptionStyleCompat { 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. * Edge type value specifying no character edges.
*/ */
public static final int EDGE_TYPE_NONE = 0; public static final int EDGE_TYPE_NONE = 0;
/** /**
* Edge type value specifying uniformly outlined character edges. * Edge type value specifying uniformly outlined character edges.
*/ */
public static final int EDGE_TYPE_OUTLINE = 1; public static final int EDGE_TYPE_OUTLINE = 1;
/** /**
* Edge type value specifying drop-shadowed character edges. * Edge type value specifying drop-shadowed character edges.
*/ */
public static final int EDGE_TYPE_DROP_SHADOW = 2; public static final int EDGE_TYPE_DROP_SHADOW = 2;
/** /**
* Edge type value specifying raised bevel character edges. * Edge type value specifying raised bevel character edges.
*/ */
public static final int EDGE_TYPE_RAISED = 3; public static final int EDGE_TYPE_RAISED = 3;
/** /**
* Edge type value specifying depressed bevel character edges. * Edge type value specifying depressed bevel character edges.
*/ */
@ -88,6 +94,7 @@ public final class CaptionStyleCompat {
* <li>{@link #EDGE_TYPE_DEPRESSED} * <li>{@link #EDGE_TYPE_DEPRESSED}
* </ul> * </ul>
*/ */
@EdgeType
public final int edgeType; public final int edgeType;
/** /**
@ -126,8 +133,8 @@ public final class CaptionStyleCompat {
* @param edgeColor See {@link #edgeColor}. * @param edgeColor See {@link #edgeColor}.
* @param typeface See {@link #typeface}. * @param typeface See {@link #typeface}.
*/ */
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType, public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor,
int edgeColor, Typeface typeface) { @EdgeType int edgeType, int edgeColor, Typeface typeface) {
this.foregroundColor = foregroundColor; this.foregroundColor = foregroundColor;
this.backgroundColor = backgroundColor; this.backgroundColor = backgroundColor;
this.windowColor = windowColor; this.windowColor = windowColor;
@ -137,6 +144,7 @@ public final class CaptionStyleCompat {
} }
@TargetApi(19) @TargetApi(19)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV19( private static CaptionStyleCompat createFromCaptionStyleV19(
CaptioningManager.CaptionStyle captionStyle) { CaptioningManager.CaptionStyle captionStyle) {
return new CaptionStyleCompat( return new CaptionStyleCompat(
@ -145,6 +153,7 @@ public final class CaptionStyleCompat {
} }
@TargetApi(21) @TargetApi(21)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV21( private static CaptionStyleCompat createFromCaptionStyleV21(
CaptioningManager.CaptionStyle captionStyle) { CaptioningManager.CaptionStyle captionStyle) {
return new CaptionStyleCompat( return new CaptionStyleCompat(

View File

@ -15,7 +15,10 @@
*/ */
package com.google.android.exoplayer2.text; package com.google.android.exoplayer2.text;
import android.support.annotation.IntDef;
import android.text.Layout.Alignment; 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. * Contains information about a specific cue, including textual content and formatting data.
@ -26,6 +29,13 @@ public class Cue {
* An unset position or width. * An unset position or width.
*/ */
public static final float DIMEN_UNSET = Float.MIN_VALUE; 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. * An unset anchor or line type value.
*/ */
@ -44,6 +54,13 @@ public class Cue {
* box. * box.
*/ */
public static final int ANCHOR_TYPE_END = 2; 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. * 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 * -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. * and end of the viewport are the top and bottom respectively.
*/ */
@LineType
public final int lineType; public final int lineType;
/** /**
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, * 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 * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
* respectively. * respectively.
*/ */
@AnchorType
public final int lineAnchor; public final int lineAnchor;
/** /**
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in * 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 * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
* respectively. * respectively.
*/ */
@AnchorType
public final int positionAnchor; public final int positionAnchor;
/** /**
* The size of the cue box in the writing direction specified as a fraction of the viewport size * 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 positionAnchor See {@link #positionAnchor}.
* @param size See {@link #size}. * @param size See {@link #size}.
*/ */
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor, public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
float position, int positionAnchor, float size) { @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
this.text = text; this.text = text;
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.line = line; this.line = line;

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer2.text; package com.google.android.exoplayer2.text;
import com.google.android.exoplayer2.Format; 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.subrip.SubripDecoder;
import com.google.android.exoplayer2.text.ttml.TtmlDecoder; import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory {
* <li>TTML ({@link TtmlDecoder})</li> * <li>TTML ({@link TtmlDecoder})</li>
* <li>SubRip ({@link SubripDecoder})</li> * <li>SubRip ({@link SubripDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})</li> * <li>TX3G ({@link Tx3gDecoder})</li>
* <li>Eia608 ({@link Eia608Decoder})</li> * <li>Cea608 ({@link Cea608Decoder})</li>
* </ul> * </ul>
*/ */
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory {
return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder"); return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder");
case MimeTypes.APPLICATION_TX3G: case MimeTypes.APPLICATION_TX3G:
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
case MimeTypes.APPLICATION_EIA608: case MimeTypes.APPLICATION_CEA608:
return Class.forName("com.google.android.exoplayer2.text.eia608.Eia608Decoder"); return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
default: default:
return null; return null;
} }

View File

@ -13,26 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.text.eia608; package com.google.android.exoplayer2.text.cea;
import android.text.TextUtils; 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.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer; 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 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 NTSC_CC_FIELD_1 = 0x00;
private static final int NUM_OUTPUT_BUFFERS = 2; private static final int CC_VALID_FLAG = 0x04;
private static final int PAYLOAD_TYPE_CC = 4; private static final int PAYLOAD_TYPE_CC = 4;
private static final int COUNTRY_CODE = 0xB5; 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 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 ParsableByteArray ccData;
private final StringBuilder captionStringBuilder; private final StringBuilder captionStringBuilder;
private long playbackPositionUs;
private SubtitleInputBuffer dequeuedInputBuffer;
private int captionMode; private int captionMode;
private int captionRowCount; private int captionRowCount;
private String captionString; private String captionString;
@ -181,17 +169,7 @@ public final class Eia608Decoder implements SubtitleDecoder {
private byte repeatableControlCc1; private byte repeatableControlCc1;
private byte repeatableControlCc2; private byte repeatableControlCc2;
public Eia608Decoder() { public Cea608Decoder() {
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<>();
ccData = new ParsableByteArray(); ccData = new ParsableByteArray();
captionStringBuilder = new StringBuilder(); captionStringBuilder = new StringBuilder();
@ -202,101 +180,20 @@ public final class Eia608Decoder implements SubtitleDecoder {
@Override @Override
public String getName() { public String getName() {
return "Eia608Decoder"; return "Cea608Decoder";
}
@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);
} }
@Override @Override
public void flush() { public void flush() {
super.flush();
setCaptionMode(CC_MODE_UNKNOWN); setCaptionMode(CC_MODE_UNKNOWN);
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
playbackPositionUs = 0;
captionStringBuilder.setLength(0); captionStringBuilder.setLength(0);
captionString = null; captionString = null;
lastCaptionString = null; lastCaptionString = null;
repeatableControlSet = false; repeatableControlSet = false;
repeatableControlCc1 = 0; repeatableControlCc1 = 0;
repeatableControlCc2 = 0; repeatableControlCc2 = 0;
while (!queuedInputBuffers.isEmpty()) {
releaseInputBuffer(queuedInputBuffers.pollFirst());
}
if (dequeuedInputBuffer != null) {
releaseInputBuffer(dequeuedInputBuffer);
dequeuedInputBuffer = null;
}
} }
@Override @Override
@ -304,14 +201,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
// Do nothing // 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()); ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
boolean captionDataProcessed = false; boolean captionDataProcessed = false;
boolean isRepeatableControl = false; boolean isRepeatableControl = false;
while (ccData.bytesLeft() > 0) { while (ccData.bytesLeft() > 0) {
byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07);
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
byte ccData2 = (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. // Ignore empty captions.
if (ccData1 == 0 && ccData2 == 0) { if (ccData1 == 0 && ccData2 == 0) {
continue; continue;
@ -320,26 +236,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
captionDataProcessed = true; captionDataProcessed = true;
// Special North American character set. // Special North American character set.
// ccData1 - P|0|0|1|C|0|0|1
// ccData2 - P|0|1|1|X|X|X|X // ccData2 - P|0|1|1|X|X|X|X
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) { if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
// TODO: Make use of the channel bit
captionStringBuilder.append(getSpecialChar(ccData2)); captionStringBuilder.append(getSpecialChar(ccData2));
continue; 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 // ccData2 - P|0|1|X|X|X|X|X
if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) { if ((ccData2 & 0x60) == 0x20) {
backspace(); // Remove standard equivalent of the special extended char. // Extended Spanish/Miscellaneous and French character set (S = 0).
captionStringBuilder.append(getExtendedEsFrChar(ccData2)); if (ccData1 == 0x12 || ccData1 == 0x1A) {
continue; // 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. // Extended Portuguese and German/Danish character set (S = 1).
// ccData2 - P|0|1|X|X|X|X|X if (ccData1 == 0x13 || ccData1 == 0x1B) {
if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) { // TODO: Make use of the channel bit
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedPtDeChar(ccData2)); captionStringBuilder.append(getExtendedPtDeChar(ccData2));
continue; continue;
}
} }
// Control character. // Control character.
@ -367,15 +290,17 @@ public final class Eia608Decoder implements SubtitleDecoder {
private boolean handleCtrl(byte cc1, byte cc2) { private boolean handleCtrl(byte cc1, byte cc2) {
boolean isRepeatableControl = isRepeatable(cc1); boolean isRepeatableControl = isRepeatable(cc1);
if (isRepeatableControl && repeatableControlSet if (isRepeatableControl) {
&& repeatableControlCc1 == cc1 if (repeatableControlSet
&& repeatableControlCc2 == cc2) { && repeatableControlCc1 == cc1
repeatableControlSet = false; && repeatableControlCc2 == cc2) {
return true; repeatableControlSet = false;
} else if (isRepeatableControl) { return true;
repeatableControlSet = true; } else {
repeatableControlCc1 = cc1; repeatableControlSet = true;
repeatableControlCc2 = cc2; repeatableControlCc1 = cc1;
repeatableControlCc2 = cc2;
}
} }
if (isMiscCode(cc1, cc2)) { if (isMiscCode(cc1, cc2)) {
handleMiscCode(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> * <p>
* The position of {@code payload} is left unchanged. * The position of {@code payload} is left unchanged.
* *
* @param payloadType The payload type of the message. * @param payloadType The payload type of the message.
* @param payloadLength The length of the payload. * @param payloadLength The length of the payload.
* @param payload A {@link ParsableByteArray} containing 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) { ParsableByteArray payload) {
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
return false; return false;

View File

@ -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);
}

View File

@ -13,22 +13,21 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; 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. * @param owner The decoder that owns this buffer.
*/ */
public Eia608SubtitleOutputBuffer(Eia608Decoder owner) { public CeaOutputBuffer(CeaDecoder owner) {
super(); super();
this.owner = owner; this.owner = owner;
} }

View File

@ -13,26 +13,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.Cue;
import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.Subtitle;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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) { public CeaSubtitle(Cue cue) {
this.text = text; if (cue == null) {
cues = Collections.emptyList();
} else {
cues = Collections.singletonList(cue);
}
} }
@Override @Override
@ -52,11 +55,8 @@ import java.util.List;
@Override @Override
public List<Cue> getCues(long timeUs) { public List<Cue> getCues(long timeUs) {
if (TextUtils.isEmpty(text)) { return cues;
return Collections.emptyList();
} else {
return Collections.singletonList(new Cue(text));
}
} }
} }

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.text.Cue;
public final float position; public final float position;
public final float line; public final float line;
@Cue.LineType
public final int lineType; public final int lineType;
public final float width; 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); 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.position = position;
this.line = line; this.line = line;
this.lineType = lineType; this.lineType = lineType;

View File

@ -16,8 +16,11 @@
package com.google.android.exoplayer2.text.ttml; package com.google.android.exoplayer2.text.ttml;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.text.Layout; import android.text.Layout;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Style object of a <code>TtmlNode</code> * Style object of a <code>TtmlNode</code>
@ -26,15 +29,25 @@ import com.google.android.exoplayer2.util.Assertions;
public static final int UNSPECIFIED = -1; 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_NORMAL = Typeface.NORMAL;
public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_BOLD = Typeface.BOLD;
public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_ITALIC = Typeface.ITALIC;
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_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_PIXEL = 1;
public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3; 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 OFF = 0;
private static final int ON = 1; private static final int ON = 1;
@ -43,10 +56,15 @@ import com.google.android.exoplayer2.util.Assertions;
private boolean hasFontColor; private boolean hasFontColor;
private int backgroundColor; private int backgroundColor;
private boolean hasBackgroundColor; private boolean hasBackgroundColor;
@OptionalBoolean
private int linethrough; private int linethrough;
@OptionalBoolean
private int underline; private int underline;
@OptionalBoolean
private int bold; private int bold;
@OptionalBoolean
private int italic; private int italic;
@FontSizeUnit
private int fontSizeUnit; private int fontSizeUnit;
private float fontSize; private float fontSize;
private String id; 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} * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
* or {@link #STYLE_BOLD_ITALIC}. * or {@link #STYLE_BOLD_ITALIC}.
*/ */
@StyleFlags
public int getStyle() { public int getStyle() {
if (bold == UNSPECIFIED && italic == UNSPECIFIED) { if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
return UNSPECIFIED; return UNSPECIFIED;
} }
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL) return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL); | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
} }
public boolean isLinethrough() { public boolean isLinethrough() {
@ -95,6 +114,18 @@ import com.google.android.exoplayer2.util.Assertions;
return this; 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() { public String getFontFamily() {
return fontFamily; return fontFamily;
} }
@ -140,18 +171,6 @@ import com.google.android.exoplayer2.util.Assertions;
return hasBackgroundColor; 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 * 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 * 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; return this;
} }
@FontSizeUnit
public int getFontSizeUnit() { public int getFontSizeUnit() {
return fontSizeUnit; return fontSizeUnit;
} }

View File

@ -16,8 +16,11 @@
package com.google.android.exoplayer2.text.webvtt; package com.google.android.exoplayer2.text.webvtt;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.text.Layout; import android.text.Layout;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -32,15 +35,25 @@ import java.util.List;
public static final int UNSPECIFIED = -1; 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_NORMAL = Typeface.NORMAL;
public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_BOLD = Typeface.BOLD;
public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_ITALIC = Typeface.ITALIC;
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_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_PIXEL = 1;
public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3; 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 OFF = 0;
private static final int ON = 1; private static final int ON = 1;
@ -56,10 +69,15 @@ import java.util.List;
private boolean hasFontColor; private boolean hasFontColor;
private int backgroundColor; private int backgroundColor;
private boolean hasBackgroundColor; private boolean hasBackgroundColor;
@OptionalBoolean
private int linethrough; private int linethrough;
@OptionalBoolean
private int underline; private int underline;
@OptionalBoolean
private int bold; private int bold;
@OptionalBoolean
private int italic; private int italic;
@FontSizeUnit
private int fontSizeUnit; private int fontSizeUnit;
private float fontSize; private float fontSize;
private Layout.Alignment textAlign; private Layout.Alignment textAlign;
@ -144,12 +162,13 @@ import java.util.List;
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
* or {@link #STYLE_BOLD_ITALIC}. * or {@link #STYLE_BOLD_ITALIC}.
*/ */
@StyleFlags
public int getStyle() { public int getStyle() {
if (bold == UNSPECIFIED && italic == UNSPECIFIED) { if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
return UNSPECIFIED; return UNSPECIFIED;
} }
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL) return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL); | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
} }
public boolean isLinethrough() { public boolean isLinethrough() {
@ -169,6 +188,15 @@ import java.util.List;
this.underline = underline ? ON : OFF; this.underline = underline ? ON : OFF;
return this; 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() { public String getFontFamily() {
return fontFamily; return fontFamily;
@ -213,16 +241,6 @@ import java.util.List;
return hasBackgroundColor; 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() { public Layout.Alignment getTextAlign() {
return textAlign; return textAlign;
} }
@ -242,6 +260,7 @@ import java.util.List;
return this; return this;
} }
@FontSizeUnit
public int getFontSizeUnit() { public int getFontSizeUnit() {
return fontSizeUnit; return fontSizeUnit;
} }

View File

@ -37,8 +37,9 @@ import com.google.android.exoplayer2.text.Cue;
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
} }
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment, public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) { 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); super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
this.startTime = startTime; this.startTime = startTime;
this.endTime = endTime; this.endTime = endTime;

View File

@ -18,22 +18,301 @@ package com.google.android.exoplayer2.trackselection;
import android.content.Context; import android.content.Context;
import android.graphics.Point; import android.graphics.Point;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; 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 { 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 * 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 * 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 static final int[] NO_TRACKS = new int[0];
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private final AtomicReference<Parameters> params;
// 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;
/** /**
* Constructs an instance that does not support adaptive video. * Constructs an instance that does not support adaptive video.
@ -82,142 +346,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
super(eventHandler); super(eventHandler);
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
allowNonSeamlessAdaptiveness = true; params = new AtomicReference<>(new Parameters());
exceedVideoConstraintsIfNecessary = true;
maxVideoWidth = Integer.MAX_VALUE;
maxVideoHeight = Integer.MAX_VALUE;
viewportWidth = Integer.MAX_VALUE;
viewportHeight = Integer.MAX_VALUE;
orientationMayChange = true;
} }
/** /**
* 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 * @param params The parameters for track selection.
* select the default track, or first track if there's no default.
*/ */
public void setPreferredLanguages(String preferredAudioLanguage) { public void setParameters(Parameters params) {
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); if (!this.params.get().equals(params)) {
if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) { this.params.set(Assertions.checkNotNull(params));
this.preferredAudioLanguage = preferredAudioLanguage;
invalidate(); 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 * @return The current selection parameters.
* select the default track, or no track if there's no default.
*/ */
public void setPreferredTextLanguage(String preferredTextLanguage) { public Parameters getParameters() {
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); return params.get();
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);
} }
// MappingTrackSelector implementation. // MappingTrackSelector implementation.
@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector {
throws ExoPlaybackException { throws ExoPlaybackException {
// Make a track selection for each renderer. // Make a track selection for each renderer.
TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length]; TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length];
Parameters params = this.params.get();
for (int i = 0; i < rendererCapabilities.length; i++) { for (int i = 0; i < rendererCapabilities.length; i++) {
switch (rendererCapabilities[i].getTrackType()) { switch (rendererCapabilities[i].getTrackType()) {
case C.TRACK_TYPE_VIDEO: case C.TRACK_TYPE_VIDEO:
rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i],
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight, rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth,
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth, params.maxVideoHeight, params.allowNonSeamlessAdaptiveness,
viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory, params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight,
exceedVideoConstraintsIfNecessary); params.orientationMayChange, adaptiveVideoTrackSelectionFactory,
params.exceedVideoConstraintsIfNecessary);
break; break;
case C.TRACK_TYPE_AUDIO: case C.TRACK_TYPE_AUDIO:
rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i],
rendererFormatSupports[i], preferredAudioLanguage); rendererFormatSupports[i], params.preferredAudioLanguage);
break; break;
case C.TRACK_TYPE_TEXT: case C.TRACK_TYPE_TEXT:
rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i],
rendererFormatSupports[i], preferredTextLanguage, preferredAudioLanguage); rendererFormatSupports[i], params.preferredTextLanguage,
params.preferredAudioLanguage);
break; break;
default: default:
rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(),
@ -442,7 +595,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) { if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(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; int trackScore;
if (formatHasLanguage(format, preferredAudioLanguage)) { if (formatHasLanguage(format, preferredAudioLanguage)) {
if (isDefault) { if (isDefault) {
@ -480,8 +633,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) { if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(trackIndex); Format format = trackGroup.getFormat(trackIndex);
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0; boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
boolean isForced = (format.selectionFlags & Format.SELECTION_FLAG_FORCED) != 0; boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0;
int trackScore; int trackScore;
if (formatHasLanguage(format, preferredTextLanguage)) { if (formatHasLanguage(format, preferredTextLanguage)) {
if (isDefault) { if (isDefault) {
@ -530,7 +683,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) { if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(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; int trackScore = isDefault ? 2 : 1;
if (trackScore > selectedTrackScore) { if (trackScore > selectedTrackScore) {
selectedGroup = trackGroup; selectedGroup = trackGroup;

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