Commit f8a8302f by ojw28 Committed by GitHub

Merge pull request #1879 from google/dev-v2

r2.0.1
parents 7d991cef c381093a
Showing with 1516 additions and 549 deletions
...@@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates. ...@@ -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'
......
# 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
......
...@@ -39,14 +39,14 @@ android { ...@@ -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')
} }
...@@ -16,8 +16,8 @@ ...@@ -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"/>
......
...@@ -16,9 +16,33 @@ ...@@ -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);
}
} }
...@@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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++) {
......
...@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -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; ...@@ -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; ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
......
...@@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 {
......
...@@ -100,7 +100,8 @@ public final class CronetDataSourceTest { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 {
......
...@@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -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 ...@@ -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();
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -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 { ...@@ -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(
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus; ...@@ -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 { ...@@ -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(
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9; ...@@ -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 { ...@@ -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(
......
...@@ -55,6 +55,7 @@ dependencies { ...@@ -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 { ...@@ -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'
} }
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
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
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
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
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
...@@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase { ...@@ -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());
}
} }
...@@ -16,8 +16,16 @@ ...@@ -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 { ...@@ -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 { ...@@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase {
} }
} }
private static final class CustomEsReader extends ElementaryStreamReader {
public int packetsRead = 0;
public CustomEsReader(TrackOutput output, String language) {
super(output);
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
language, null, 0));
}
@Override
public void seek() {
}
@Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
}
@Override
public void consume(ParsableByteArray data) {
}
@Override
public void packetFinished() {
packetsRead++;
}
public TrackOutput getTrackOutput() {
return output;
}
}
private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory {
private final ElementaryStreamReader.Factory defaultFactory;
private CustomEsReader reader;
public CustomEsReaderFactory() {
defaultFactory = new DefaultStreamReaderFactory();
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if (streamType == 3) {
// We need to manually avoid a duplicate custom reader creation.
if (reader == null) {
reader = new CustomEsReader(output.track(pid), esInfo.language);
}
return reader;
} else {
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output);
}
}
}
} }
...@@ -17,8 +17,11 @@ package com.google.android.exoplayer2; ...@@ -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;
/** /**
...@@ -71,54 +74,78 @@ public final class C { ...@@ -71,54 +74,78 @@ 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
*/ */
...@@ -133,47 +160,92 @@ public final class C { ...@@ -133,47 +160,92 @@ public final class C {
? 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 { ...@@ -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 { ...@@ -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 { ...@@ -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.
...@@ -364,15 +419,19 @@ public final class C { ...@@ -364,15 +419,19 @@ 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.
*/ */
......
...@@ -16,7 +16,7 @@ ...@@ -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 { ...@@ -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) {
......
...@@ -15,9 +15,12 @@ ...@@ -15,9 +15,12 @@
*/ */
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.
...@@ -25,6 +28,12 @@ import java.io.IOException; ...@@ -25,6 +28,12 @@ import java.io.IOException;
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>
* Call {@link #getSourceException()} to retrieve the underlying cause. * Call {@link #getSourceException()} to retrieve the underlying cause.
...@@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -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 { ...@@ -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;
......
...@@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -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();
} }
} }
......
...@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { ...@@ -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 { ...@@ -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}
......
...@@ -40,20 +40,6 @@ public final class Format implements Parcelable { ...@@ -40,20 +40,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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -17,8 +17,7 @@ package com.google.android.exoplayer2; ...@@ -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 { ...@@ -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.
......
...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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)
......
...@@ -208,7 +208,9 @@ public final class AudioTrack { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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) {
......
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
......
...@@ -16,6 +16,7 @@ ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -15,7 +15,10 @@ ...@@ -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;
/** /**
...@@ -24,15 +27,20 @@ import java.nio.ByteBuffer; ...@@ -24,15 +27,20 @@ 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 { ...@@ -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 { ...@@ -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;
} }
......
...@@ -16,6 +16,9 @@ ...@@ -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.
...@@ -24,6 +27,12 @@ import android.annotation.TargetApi; ...@@ -24,6 +27,12 @@ import android.annotation.TargetApi;
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.
*/ */
int STATE_ERROR = 0; int STATE_ERROR = 0;
...@@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> { ...@@ -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();
/** /**
......
...@@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> { ...@@ -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);
} }
...@@ -40,7 +40,7 @@ import java.util.UUID; ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
} }
......
...@@ -15,12 +15,22 @@ ...@@ -15,12 +15,22 @@
*/ */
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.
*/ */
public static final int REASON_UNSUPPORTED_SCHEME = 1; public static final int REASON_UNSUPPORTED_SCHEME = 1;
...@@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception { ...@@ -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 { ...@@ -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;
} }
......
...@@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput { ...@@ -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 { ...@@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput {
} }
} }
peekBufferPosition += length; peekBufferPosition += length;
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
return true; return true;
} }
......
...@@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput { ...@@ -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.
} }
......
...@@ -72,6 +72,7 @@ public interface TrackOutput { ...@@ -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);
} }
...@@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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);
......
...@@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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();
......
...@@ -15,6 +15,7 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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) {
......
...@@ -15,6 +15,7 @@ ...@@ -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; ...@@ -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;
...@@ -65,6 +68,13 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -65,6 +68,13 @@ public final class FragmentedMp4Extractor implements Extractor {
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
* the first sample in each segment. * the first sample in each segment.
...@@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -100,13 +100,14 @@ import java.io.IOException; ...@@ -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; ...@@ -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++) {
......
...@@ -15,8 +15,11 @@ ...@@ -15,8 +15,11 @@
*/ */
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.
...@@ -24,6 +27,12 @@ import com.google.android.exoplayer2.Format; ...@@ -24,6 +27,12 @@ import com.google.android.exoplayer2.Format;
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.
*/ */
public static final int TRANSFORMATION_NONE = 0; public static final int TRANSFORMATION_NONE = 0;
...@@ -66,6 +75,7 @@ public final class Track { ...@@ -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 { ...@@ -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;
......
...@@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util; ...@@ -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);
......
...@@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -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 { ...@@ -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 { ...@@ -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) {
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef;
import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Default implementation for {@link ElementaryStreamReader.Factory}.
*/
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
/**
* Flags controlling what workarounds are enabled for elementary stream readers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM,
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE})
public @interface WorkaroundFlags {
}
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
public static final int WORKAROUND_MAP_BY_TYPE = 16;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1.
private final SparseBooleanArray trackIds;
@WorkaroundFlags
private final int workaroundFlags;
private Id3Reader id3Reader;
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
public DefaultStreamReaderFactory() {
this(0);
}
public DefaultStreamReaderFactory(int workaroundFlags) {
trackIds = new SparseBooleanArray();
this.workaroundFlags = workaroundFlags;
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See b/20261500.
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
}
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
if (trackIds.get(trackId)) {
return null;
}
trackIds.put(trackId, true);
switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new MpegAudioReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AAC:
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new Ac3Reader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
return new DtsReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_H262:
return new H262Reader(output.track(trackId));
case TsExtractor.TS_STREAM_TYPE_H264:
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0
? null : new H264Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)),
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
case TsExtractor.TS_STREAM_TYPE_H265:
return new H265Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)));
case TsExtractor.TS_STREAM_TYPE_ID3:
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
return id3Reader;
} else {
return new Id3Reader(output.track(nextEmbeddedTrackId++));
}
default:
return null;
}
}
}
...@@ -15,13 +15,60 @@ ...@@ -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;
......
...@@ -128,7 +128,7 @@ import java.util.Collections; ...@@ -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; ...@@ -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;
......
...@@ -57,7 +57,7 @@ import java.util.List; ...@@ -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; ...@@ -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);
} }
......
...@@ -64,7 +64,7 @@ import java.util.Collections; ...@@ -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; ...@@ -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);
} }
......
...@@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor { ...@@ -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 { ...@@ -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 { ...@@ -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());
} }
......
...@@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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);
......
...@@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C; ...@@ -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; ...@@ -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; ...@@ -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;
} }
......
...@@ -87,7 +87,7 @@ import java.io.IOException; ...@@ -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;
......
...@@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -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 { ...@@ -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());
} }
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; ...@@ -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 { ...@@ -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;
} }
......
...@@ -61,12 +61,15 @@ import java.util.Arrays; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 {
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -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 ...@@ -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
......
...@@ -19,7 +19,6 @@ import android.util.Log; ...@@ -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 { ...@@ -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
......
...@@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -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.
* *
...@@ -43,6 +43,17 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -43,6 +43,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.
* <p> * <p>
......
...@@ -16,7 +16,6 @@ ...@@ -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 { ...@@ -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.
......
...@@ -28,20 +28,27 @@ import java.util.IdentityHashMap; ...@@ -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
......
...@@ -15,11 +15,12 @@ ...@@ -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;
...@@ -37,10 +38,15 @@ public final class MergingMediaSource implements MediaSource { ...@@ -37,10 +38,15 @@ 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 { ...@@ -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 { ...@@ -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
......
...@@ -29,7 +29,7 @@ public interface SequenceableLoader { ...@@ -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);
......
...@@ -79,6 +79,11 @@ import java.util.Arrays; ...@@ -79,6 +79,11 @@ import java.util.Arrays;
} }
@Override @Override
public void prepare(Callback callback) {
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
loader.maybeThrowError(); loader.maybeThrowError();
} }
......
...@@ -19,7 +19,6 @@ import android.net.Uri; ...@@ -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 { ...@@ -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
......
...@@ -15,6 +15,7 @@ ...@@ -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 ...@@ -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);
} }
......
...@@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad ...@@ -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");
} }
......
...@@ -48,10 +48,10 @@ import java.util.List; ...@@ -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; ...@@ -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; ...@@ -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) {
...@@ -98,6 +96,12 @@ import java.util.List; ...@@ -98,6 +96,12 @@ import java.util.List;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();
} }
......
...@@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -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 { ...@@ -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;
} }
......
...@@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler ...@@ -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)) {
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor; ...@@ -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; ...@@ -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;
......
...@@ -50,11 +50,11 @@ import java.util.List; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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() {
...@@ -112,6 +108,15 @@ import java.util.List; ...@@ -112,6 +108,15 @@ import java.util.List;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
if (sampleStreamWrappers == null) { if (sampleStreamWrappers == null) {
manifestFetcher.maybeThrowError(); manifestFetcher.maybeThrowError();
...@@ -239,13 +244,7 @@ import java.util.List; ...@@ -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; ...@@ -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; ...@@ -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,
......
...@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C; ...@@ -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 { ...@@ -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
......
...@@ -15,6 +15,8 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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;
}
} }
...@@ -15,18 +15,29 @@ ...@@ -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;
} }
......
...@@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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)
......
...@@ -46,23 +46,22 @@ import java.util.ArrayList; ...@@ -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; ...@@ -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) {
...@@ -95,6 +93,12 @@ import java.util.ArrayList; ...@@ -95,6 +93,12 @@ import java.util.ArrayList;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();
} }
......
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -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, ...@@ -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;
} }
......
...@@ -18,9 +18,12 @@ package com.google.android.exoplayer2.text; ...@@ -18,9 +18,12 @@ 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}.
...@@ -28,25 +31,28 @@ import com.google.android.exoplayer2.util.Util; ...@@ -28,25 +31,28 @@ import com.google.android.exoplayer2.util.Util;
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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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(
......
...@@ -15,7 +15,10 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -16,7 +16,7 @@ ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
......
/*
* 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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment