Merge pull request #1879 from google/dev-v2

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

View File

@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates.
#### Via jCenter ####
The easiest way to get started using ExoPlayer is by including the following in
your project's `build.gradle` file:
The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the jcenter repository included in
the `build.gradle` file in the root of your project:
```gradle
repositories {
jcenter()
}
```
Next, include the following in your module's `build.gradle` file:
```gradle
compile 'com.google.android.exoplayer:exoplayer:rX.X.X'

View File

@ -1,5 +1,14 @@
# Release notes #
### r2.0.1 ###
* Fix playback of short duration content
([#1837](https://github.com/google/ExoPlayer/issues/1837)).
* Fix MergingMediaSource preparation issue
([#1853](https://github.com/google/ExoPlayer/issues/1853)).
* Fix live stream buffering (out of memory) issue
([#1825](https://github.com/google/ExoPlayer/issues/1825)).
### r2.0.0 ###
ExoPlayer 2.x is a major iteration of the library. It includes significant API

View File

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

View File

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

View File

@ -16,9 +16,33 @@
package com.google.android.exoplayer2.demo;
import android.app.Application;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
/**
* Placeholder application to facilitate overriding Application methods for debugging and testing.
*/
public class DemoApplication extends Application {
protected String userAgent;
@Override
public void onCreate() {
super.onCreate();
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
}
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultDataSourceFactory(this, bandwidthMeter,
buildHttpDataSourceFactory(bandwidthMeter));
}
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
try {
validateResponse(info);
responseInfo = info;
// Check content length.
contentLength = getContentLength(info.getAllHeaders());
// If a specific length is requested and a specific length is returned but the 2 don't match
// it's an error.
if (currentDataSpec.length != C.LENGTH_UNSET
&& contentLength != C.LENGTH_UNSET
&& currentDataSpec.length != contentLength) {
throw new OpenException("Content length did not match requested length", currentDataSpec,
getCurrentRequestStatus());
if (isCompressed(info)) {
contentLength = currentDataSpec.length;
} else {
// Check content length.
contentLength = getContentLength(info.getAllHeaders());
// If a specific length is requested and a specific length is returned but the 2 don't match
// it's an error.
if (currentDataSpec.length != C.LENGTH_UNSET
&& contentLength != C.LENGTH_UNSET
&& currentDataSpec.length != contentLength) {
throw new OpenException("Content length did not match requested length", currentDataSpec,
getCurrentRequestStatus());
}
}
if (contentLength > 0) {
@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
}
}
/**
* Returns {@code true} iff the content is compressed.
*
* <p>If {@code true}, clients cannot use the value of content length from the request headers to
* read the data, since Cronet returns the uncompressed data and this content length reflects the
* compressed content length.
*/
private boolean isCompressed(UrlResponseInfo info) {
for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) {
if (entry.getKey().equalsIgnoreCase("Content-Encoding")) {
return !entry.getValue().equalsIgnoreCase("identity");
}
}
return false;
}
private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
// Check for a valid response code.
int responseCode = info.getHttpStatusCode();

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@ -0,0 +1,33 @@
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true

View File

@ -0,0 +1,29 @@
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true

View File

@ -0,0 +1,29 @@
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true

View File

@ -0,0 +1,29 @@
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true

View File

@ -0,0 +1,33 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true

View File

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

View File

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

View File

@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
}, "mp3/bear.mp3", getInstrumentation());
}
public void testTrimmedMp3Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new Mp3Extractor();
}
}, "mp3/play-trimmed.mp3", getInstrumentation());
}
}

View File

@ -16,8 +16,16 @@
package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;
@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase {
}, "ts/sample.ts", fileData, getInstrumentation());
}
public void testCustomPesReader() throws Exception {
CustomEsReaderFactory factory = new CustomEsReaderFactory();
TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory);
FakeExtractorInput input = new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
.setSimulateIOErrors(false)
.setSimulateUnknownLength(false)
.setSimulatePartialReads(false).build();
FakeExtractorOutput output = new FakeExtractorOutput();
tsExtractor.init(output);
tsExtractor.seek(input.getPosition());
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) {
readResult = tsExtractor.read(input, seekPositionHolder);
}
CustomEsReader reader = factory.reader;
assertEquals(2, reader.packetsRead);
TrackOutput trackOutput = reader.getTrackOutput();
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
assertEquals(
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
((FakeTrackOutput) trackOutput).format);
}
private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
for (int i = 0; i < length; i++) {
if (((byte) i) == TS_SYNC_BYTE) {
@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase {
}
}
private static final class CustomEsReader extends ElementaryStreamReader {
public int packetsRead = 0;
public CustomEsReader(TrackOutput output, String language) {
super(output);
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
language, null, 0));
}
@Override
public void seek() {
}
@Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
}
@Override
public void consume(ParsableByteArray data) {
}
@Override
public void packetFinished() {
packetsRead++;
}
public TrackOutput getTrackOutput() {
return output;
}
}
private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory {
private final ElementaryStreamReader.Factory defaultFactory;
private CustomEsReader reader;
public CustomEsReaderFactory() {
defaultFactory = new DefaultStreamReaderFactory();
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if (streamType == 3) {
// We need to manually avoid a duplicate custom reader creation.
if (reader == null) {
reader = new CustomEsReader(output.track(pid), esInfo.language);
}
return reader;
} else {
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output);
}
}
}
}

View File

@ -17,8 +17,11 @@ package com.google.android.exoplayer2;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.support.annotation.IntDef;
import android.view.Surface;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
/**
@ -70,55 +73,79 @@ public final class C {
*/
public static final String UTF8_NAME = "UTF-8";
/**
* Crypto modes for a codec.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
public @interface CryptoMode {}
/**
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
/**
* @see MediaCodec#CRYPTO_MODE_AES_CTR
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
/**
* @see MediaCodec#CRYPTO_MODE_AES_CBC
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
/**
* Represents an audio encoding, or an invalid or unset value.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS,
ENCODING_DTS_HD})
public @interface Encoding {}
/**
* Represents a PCM audio encoding, or an invalid or unset value.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT})
public @interface PcmEncoding {}
/**
* @see AudioFormat#ENCODING_INVALID
*/
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/**
* @see AudioFormat#ENCODING_PCM_8BIT
*/
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/**
* @see AudioFormat#ENCODING_PCM_16BIT
*/
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/**
* PCM encoding with 24 bits per sample.
*/
public static final int ENCODING_PCM_24BIT = 0x80000000;
/**
* PCM encoding with 32 bits per sample.
*/
public static final int ENCODING_PCM_32BIT = 0x40000000;
/**
* @see AudioFormat#ENCODING_AC3
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/**
* @see AudioFormat#ENCODING_E_AC3
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
/**
* @see AudioFormat#ENCODING_DTS
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
/**
* @see AudioFormat#ENCODING_DTS_HD
*/
@ -132,48 +159,93 @@ public final class C {
public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
/**
* Flags which can apply to a buffer containing a media sample.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY})
public @interface BufferFlags {}
/**
* Indicates that a buffer holds a synchronization sample.
*/
@SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
/**
* Flag for empty buffers that signal that the end of the stream was reached.
*/
@SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/**
* Indicates that a buffer is (at least partially) encrypted.
*/
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
/**
* Indicates that a buffer should be decoded but not rendered.
*/
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000;
/**
* Track selection flags.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED,
SELECTION_FLAG_AUTOSELECT})
public @interface SelectionFlags {}
/**
* Indicates that the track should be selected if user preferences do not state otherwise.
*/
public static final int SELECTION_FLAG_DEFAULT = 1;
/**
* Indicates that the track must be displayed. Only applies to text tracks.
*/
public static final int SELECTION_FLAG_FORCED = 2;
/**
* Indicates that the player may choose to play the track in absence of an explicit user
* preference.
*/
public static final int SELECTION_FLAG_AUTOSELECT = 4;
/**
* Represents a streaming or other media type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
public @interface ContentType {}
/**
* Value returned by {@link Util#inferContentType(String)} for DASH manifests.
*/
public static final int TYPE_DASH = 0;
/**
* Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests.
*/
public static final int TYPE_SS = 1;
/**
* Value returned by {@link Util#inferContentType(String)} for HLS manifests.
*/
public static final int TYPE_HLS = 2;
/**
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
* Smooth Streaming manifests.
*/
public static final int TYPE_OTHER = 3;
/**
* A return value for methods where the end of an input was encountered.
*/
public static final int RESULT_END_OF_INPUT = -1;
/**
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
*/
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
/**
* A return value for methods where nothing was read.
*/
public static final int RESULT_NOTHING_READ = -3;
/**
* A return value for methods where a buffer was read.
*/
public static final int RESULT_BUFFER_READ = -4;
/**
* A return value for methods where a format was read.
*/
@ -183,32 +255,26 @@ public final class C {
* A data type constant for data of unknown or unspecified type.
*/
public static final int DATA_TYPE_UNKNOWN = 0;
/**
* A data type constant for media, typically containing media samples.
*/
public static final int DATA_TYPE_MEDIA = 1;
/**
* A data type constant for media, typically containing only initialization data.
*/
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
/**
* A data type constant for drm or encryption data.
*/
public static final int DATA_TYPE_DRM = 3;
/**
* A data type constant for a manifest file.
*/
public static final int DATA_TYPE_MANIFEST = 4;
/**
* A data type constant for time synchronization data.
*/
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
/**
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
* equal to this value.
@ -219,32 +285,26 @@ public final class C {
* A type constant for tracks of unknown type.
*/
public static final int TRACK_TYPE_UNKNOWN = -1;
/**
* A type constant for tracks of some default type, where the type itself is unknown.
*/
public static final int TRACK_TYPE_DEFAULT = 0;
/**
* A type constant for audio tracks.
*/
public static final int TRACK_TYPE_AUDIO = 1;
/**
* A type constant for video tracks.
*/
public static final int TRACK_TYPE_VIDEO = 2;
/**
* A type constant for text tracks.
*/
public static final int TRACK_TYPE_TEXT = 3;
/**
* A type constant for metadata tracks.
*/
public static final int TRACK_TYPE_METADATA = 4;
/**
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
* equal to this value.
@ -255,27 +315,22 @@ public final class C {
* A selection reason constant for selections whose reasons are unknown or unspecified.
*/
public static final int SELECTION_REASON_UNKNOWN = 0;
/**
* A selection reason constant for an initial track selection.
*/
public static final int SELECTION_REASON_INITIAL = 1;
/**
* A selection reason constant for an manual (i.e. user initiated) track selection.
*/
public static final int SELECTION_REASON_MANUAL = 2;
/**
* A selection reason constant for an adaptive track selection.
*/
public static final int SELECTION_REASON_ADAPTIVE = 3;
/**
* A selection reason constant for a trick play track selection.
*/
public static final int SELECTION_REASON_TRICK_PLAY = 4;
/**
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
* or equal to this value.
@ -363,16 +418,20 @@ public final class C {
*/
public static final int MSG_CUSTOM_BASE = 10000;
/**
* The stereo mode for 360/3D/VR videos.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
public @interface StereoMode {}
/**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_MONO = 0;
/**
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_TOP_BOTTOM = 1;
/**
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/

View File

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

View File

@ -15,15 +15,24 @@
*/
package com.google.android.exoplayer2;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Thrown when a non-recoverable playback failure occurs.
*/
public final class ExoPlaybackException extends Exception {
/**
* The type of source that produced the error.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})
public @interface Type {}
/**
* The error occurred loading data from a {@link MediaSource}.
* <p>
@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception {
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
* {@link #TYPE_UNEXPECTED}.
*/
@Type
public final int type;
/**
@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception {
return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET);
}
private ExoPlaybackException(int type, String message, Throwable cause, int rendererIndex) {
private ExoPlaybackException(@Type int type, String message, Throwable cause,
int rendererIndex) {
super(message, cause);
this.type = type;
this.rendererIndex = rendererIndex;

View File

@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline == null || pendingSeekAcks > 0) {
return maskingWindowPositionMs;
} else {
int periodIndex = playbackInfo.periodIndex;
timeline.getPeriod(periodIndex, period);
int windowIndex = period.windowIndex;
timeline.getWindow(windowIndex, window);
if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
&& window.getPositionInFirstPeriodUs() == 0
&& window.getDurationUs() == period.getDurationUs()) {
return C.usToMs(playbackInfo.bufferedPositionUs);
}
return getCurrentPosition();
timeline.getPeriod(playbackInfo.periodIndex, period);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
}
}

View File

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

View File

@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
String VERSION = "2.0.0";
String VERSION = "2.0.1";
/**
* The version of the library, expressed as an integer.
@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006).
*/
int VERSION_INT = 2000000;
int VERSION_INT = 2000001;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -39,20 +39,6 @@ public final class Format implements Parcelable {
*/
public static final int NO_VALUE = -1;
/**
* Indicates that the track should be selected if user preferences do not state otherwise.
*/
public static final int SELECTION_FLAG_DEFAULT = 1;
/**
* Indicates that the track must be displayed. Only applies to text tracks.
*/
public static final int SELECTION_FLAG_FORCED = 2;
/**
* Indicates that the player may choose to play the track in absence of an explicit user
* preference.
*/
public static final int SELECTION_FLAG_AUTOSELECT = 4;
/**
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples.
@ -131,6 +117,7 @@ public final class Format implements Parcelable {
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}.
*/
@C.StereoMode
public final int stereoMode;
/**
* The projection data for 360/VR video, or null if not applicable.
@ -153,6 +140,7 @@ public final class Format implements Parcelable {
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
* other media types.
*/
@C.PcmEncoding
public final int pcmEncoding;
/**
* The number of samples to trim from the start of the decoded audio stream.
@ -177,6 +165,7 @@ public final class Format implements Parcelable {
/**
* Track selection flags.
*/
@C.SelectionFlags
public final int selectionFlags;
/**
@ -218,7 +207,7 @@ public final class Format implements Parcelable {
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate,
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
byte[] projectionData, int stereoMode, DrmInitData drmInitData) {
byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData,
@ -229,7 +218,7 @@ public final class Format implements Parcelable {
public static Format createAudioContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, int selectionFlags, String language) {
List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData,
@ -238,25 +227,26 @@ public final class Format implements Parcelable {
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
String language) {
List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
}
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
String language) {
int bitrate, int maxInputSize, int channelCount, int sampleRate,
@C.PcmEncoding int pcmEncoding, List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
selectionFlags, language);
}
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
int encoderDelay, int encoderPadding, List<byte[]> initializationData,
DrmInitData drmInitData, int selectionFlags, String language) {
int bitrate, int maxInputSize, int channelCount, int sampleRate,
@C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding,
List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
@ -266,20 +256,21 @@ public final class Format implements Parcelable {
// Text.
public static Format createTextContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int selectionFlags, String language) {
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int selectionFlags, String language, DrmInitData drmInitData) {
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
drmInitData, OFFSET_SAMPLE_RELATIVE);
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int selectionFlags, String language, DrmInitData drmInitData,
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
@ -313,10 +304,10 @@ public final class Format implements Parcelable {
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount,
int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags,
String language, long subsampleOffsetUs, List<byte[]> initializationData,
DrmInitData drmInitData) {
float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
long subsampleOffsetUs, List<byte[]> initializationData, DrmInitData drmInitData) {
this.id = id;
this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType;
@ -343,6 +334,7 @@ public final class Format implements Parcelable {
this.drmInitData = drmInitData;
}
@SuppressWarnings("ResourceType")
/* package */ Format(Parcel in) {
id = in.readString();
containerMimeType = in.readString();
@ -388,8 +380,8 @@ public final class Format implements Parcelable {
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
}
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
int selectionFlags, String language) {
public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
@C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
@ -402,7 +394,7 @@ public final class Format implements Parcelable {
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,9 @@
package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A DRM session.
@ -23,6 +26,12 @@ import android.annotation.TargetApi;
@TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> {
/**
* The state of the DRM session.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
@interface State {}
/**
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
*/
@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
*/
@State
int getState();
/**

View File

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

View File

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

View File

@ -15,11 +15,21 @@
*/
package com.google.android.exoplayer2.drm;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Thrown when the requested DRM scheme is not supported.
*/
public final class UnsupportedDrmException extends Exception {
/**
* The reason for the exception.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
public @interface Reason {}
/**
* The requested DRM scheme is unsupported by the device.
*/
@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception {
/**
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/
@Reason
public final int reason;
/**
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/
public UnsupportedDrmException(int reason) {
public UnsupportedDrmException(@Reason int reason) {
this.reason = reason;
}
@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception {
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
* @param cause The cause of this exception.
*/
public UnsupportedDrmException(int reason, Exception cause) {
public UnsupportedDrmException(@Reason int reason, Exception cause) {
super(cause);
this.reason = reason;
}

View File

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

View File

@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput {
long offset = extrasHolder.offset;
// Read the signal byte.
scratch.reset(1);
readData(offset, scratch.data, 1);
offset++;
byte signalByte = scratch.data[0];
@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput {
// Read the subsample count, if present.
int subsampleCount;
if (subsampleEncryption) {
scratch.reset(2);
readData(offset, scratch.data, 2);
offset += 2;
scratch.setPosition(0);
subsampleCount = scratch.readUnsignedShort();
} else {
subsampleCount = 1;
@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput {
}
if (subsampleEncryption) {
int subsampleDataLength = 6 * subsampleCount;
ensureCapacity(scratch, subsampleDataLength);
scratch.reset(subsampleDataLength);
readData(offset, scratch.data, subsampleDataLength);
offset += subsampleDataLength;
scratch.setPosition(0);
@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput {
}
}
/**
* Ensure that the passed {@link ParsableByteArray} is of at least the specified limit.
*/
private static void ensureCapacity(ParsableByteArray byteArray, int limit) {
if (byteArray.limit() < limit) {
byteArray.reset(new byte[limit], limit);
}
}
// Called by the loading thread.
/**
@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput {
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs);
return;
@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput {
}
}
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
byte[] encryptionKey) {
public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
int size, byte[] encryptionKey) {
Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs);
timesUs[relativeWriteIndex] = timeUs;

View File

@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput {
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
// Do nothing.
}

View File

@ -72,6 +72,7 @@ public interface TrackOutput {
* whose metadata is being passed.
* @param encryptionKey The encryption key associated with the sample. May be null.
*/
void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey);
void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey);
}

View File

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

View File

@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor {
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) {
return RESULT_END_OF_INPUT;
if (synchronizedHeaderData == 0) {
try {
synchronize(input, false);
} catch (EOFException e) {
return RESULT_END_OF_INPUT;
}
}
if (seeker == null) {
seeker = setupSeeker(input);
@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor {
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) {
if (!maybeResynchronize(extractorInput)) {
extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
return RESULT_END_OF_INPUT;
}
scratch.setPosition(0);
int sampleHeaderData = scratch.readInt();
if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK)
|| MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
extractorInput.skipFully(1);
synchronizedHeaderData = 0;
return RESULT_CONTINUE;
}
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
if (basisTimeUs == C.TIME_UNSET) {
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor {
return RESULT_CONTINUE;
}
/**
* Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary.
*/
private boolean maybeResynchronize(ExtractorInput extractorInput)
throws IOException, InterruptedException {
extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
return false;
}
scratch.setPosition(0);
int sampleHeaderData = scratch.readInt();
if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) {
int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData);
if (frameSize != C.LENGTH_UNSET) {
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
return true;
}
}
synchronizedHeaderData = 0;
extractorInput.skipFully(1);
return synchronizeCatchingEndOfInput(extractorInput);
}
private boolean synchronizeCatchingEndOfInput(ExtractorInput input)
throws IOException, InterruptedException {
// An EOFException will be raised if any peek operation was partially satisfied. If a seek
// operation resulted in reading from within the last frame, we may try to peek past the end of
// the file in a partially-satisfied read operation, so we need to catch the exception.
try {
return synchronize(input, false);
} catch (EOFException e) {
return false;
}
}
private boolean synchronize(ExtractorInput input, boolean sniffing)
throws IOException, InterruptedException {
int searched = 0;
int validFrameCount = 0;
int candidateSynchronizedHeaderData = 0;
int peekedId3Bytes = 0;
int searchedBytes = 0;
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition();
if (input.getPosition() == 0) {
Id3Util.parseId3(input, gaplessInfoHolder);
@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor {
}
}
while (true) {
if (sniffing && searched == MAX_SNIFF_BYTES) {
return false;
}
if (!sniffing && searched == MAX_SYNC_BYTES) {
throw new ParserException("Searched too many bytes.");
}
if (!input.peekFully(scratch.data, 0, 4, true)) {
return false;
if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
// We reached the end of the stream but found at least one valid frame.
break;
}
scratch.setPosition(0);
int headerData = scratch.readInt();
@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor {
if ((candidateSynchronizedHeaderData != 0
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
// The header is invalid or doesn't match the candidate header. Try the next byte offset.
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
if (searchedBytes++ == searchLimitBytes) {
if (!sniffing) {
throw new ParserException("Searched too many bytes.");
}
return false;
}
validFrameCount = 0;
candidateSynchronizedHeaderData = 0;
searched++;
if (sniffing) {
input.resetPeekPosition();
input.advancePeekPosition(peekedId3Bytes + searched);
input.advancePeekPosition(peekedId3Bytes + searchedBytes);
} else {
input.skipFully(1);
}
} else {
// The header is valid and matches the candidate header.
// The header matches the candidate header and/or is valid.
validFrameCount++;
if (validFrameCount == 1) {
MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor {
}
// Prepare to read the synchronized frame.
if (sniffing) {
input.skipFully(peekedId3Bytes + searched);
input.skipFully(peekedId3Bytes + searchedBytes);
} else {
input.resetPeekPosition();
}
@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor {
long position = input.getPosition();
long length = input.getLength();
int headerData = 0;
Seeker seeker = null;
// Check if there is a Xing header.
int xingBase = (synchronizedHeader.version & 1) != 0
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
frame.setPosition(xingBase);
int headerData = frame.readInt();
Seeker seeker = null;
if (frame.limit() >= xingBase + 4) {
frame.setPosition(xingBase);
headerData = frame.readInt();
}
if (headerData == XING_HEADER || headerData == INFO_HEADER) {
seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor {
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
}
input.skipFully(synchronizedHeader.frameSize);
} else {
} else if (frame.limit() >= 40) {
// Check if there is a VBRI header.
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
headerData = frame.readInt();

View File

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

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -64,6 +67,13 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final String TAG = "FragmentedMp4Extractor";
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
/**
* Flags controlling the behavior of the extractor.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED})
public @interface Flags {}
/**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor {
* This flag does nothing if the stream is not a video stream.
*/
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
/**
* Flag to ignore any tfdt boxes in the stream.
*/
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
/**
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
* container.
@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
// Workarounds.
@Flags
private final int flags;
private final Track sideloadedTrack;
@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor {
}
/**
* @param flags Flags to allow parsing of faulty streams.
* @param flags Flags that control the extractor's behavior.
*/
public FragmentedMp4Extractor(int flags) {
public FragmentedMp4Extractor(@Flags int flags) {
this(flags, null);
}
/**
* @param flags Flags to allow parsing of faulty streams.
* @param flags Flags that control the extractor's behavior.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data.
*/
public FragmentedMp4Extractor(int flags, Track sideloadedTrack) {
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) {
this.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
int flags, byte[] extendedTypeScratch) throws ParserException {
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
int moofContainerChildrenSize = moof.containerChildren.size();
for (int i = 0; i < moofContainerChildrenSize; i++) {
Atom.ContainerAtom child = moof.containerChildren.get(i);
@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a traf atom (defined in 14496-12).
*/
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
int flags, byte[] extendedTypeScratch) throws ParserException {
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
if (trackBundle == null) {
@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
int flags) {
@Flags int flags) {
int trunCount = 0;
int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren;
@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param trun The trun atom to decode.
* @return The starting position of samples for the next run.
*/
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags,
ParsableByteArray trun, int trackRunStart) {
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
@Flags int flags, ParsableByteArray trun, int trackRunStart) {
trun.setPosition(Atom.HEADER_SIZE);
int fullAtom = trun.readInt();
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
@C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
byte[] encryptionKey = null;

View File

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

View File

@ -15,14 +15,23 @@
*/
package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Encapsulates information describing an MP4 track.
*/
public final class Track {
/**
* The transformation to apply to samples in the track, if any.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})
public @interface Transformation {}
/**
* A no-op sample transformation.
*/
@ -66,6 +75,7 @@ public final class Track {
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
* sample.
*/
@Transformation
public final int sampleTransformation;
/**
@ -90,7 +100,7 @@ public final class Track {
public final int nalUnitLengthFieldLength;
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
Format format, int sampleTransformation,
Format format, @Transformation int sampleTransformation,
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
long[] editListDurations, long[] editListMediaTimes) {
this.id = id;

View File

@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util;
*/
public final int[] flags;
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs,
int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length);

View File

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

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef;
import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Default implementation for {@link ElementaryStreamReader.Factory}.
*/
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
/**
* Flags controlling what workarounds are enabled for elementary stream readers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM,
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE})
public @interface WorkaroundFlags {
}
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
public static final int WORKAROUND_MAP_BY_TYPE = 16;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1.
private final SparseBooleanArray trackIds;
@WorkaroundFlags
private final int workaroundFlags;
private Id3Reader id3Reader;
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
public DefaultStreamReaderFactory() {
this(0);
}
public DefaultStreamReaderFactory(int workaroundFlags) {
trackIds = new SparseBooleanArray();
this.workaroundFlags = workaroundFlags;
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See b/20261500.
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
}
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
if (trackIds.get(trackId)) {
return null;
}
trackIds.put(trackId, true);
switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new MpegAudioReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AAC:
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new Ac3Reader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
return new DtsReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_H262:
return new H262Reader(output.track(trackId));
case TsExtractor.TS_STREAM_TYPE_H264:
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0
? null : new H264Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)),
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
case TsExtractor.TS_STREAM_TYPE_H265:
return new H265Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)));
case TsExtractor.TS_STREAM_TYPE_ID3:
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
return id3Reader;
} else {
return new Id3Reader(output.track(nextEmbeddedTrackId++));
}
default:
return null;
}
}
}

View File

@ -15,13 +15,60 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
/**
* Extracts individual samples from an elementary media stream, preserving original order.
*/
/* package */ abstract class ElementaryStreamReader {
public abstract class ElementaryStreamReader {
/**
* Factory of {@link ElementaryStreamReader} instances.
*/
public interface Factory {
/**
* Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the
* stream type is not supported or if the stream already has a reader assigned to it.
*
* @param pid The pid for the PMT entry.
* @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining
* the type of the stream.
* @param esInfo The descriptor information linked to the elementary stream.
* @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the
* created readers.
* @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided
* pid. {@code null} if the stream is not supported or if it should be ignored.
*/
ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo,
ExtractorOutput output);
}
/**
* Holds descriptor information associated with an elementary stream.
*/
public static final class EsInfo {
public final int streamType;
public String language;
public byte[] descriptorBytes;
/**
* @param streamType The type of the stream as defined by the
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
* @param descriptorBytes The descriptor bytes associated to the stream.
*/
public EsInfo(int streamType, String language, byte[] descriptorBytes) {
this.streamType = streamType;
this.language = language;
this.descriptorBytes = descriptorBytes;
}
}
protected final TrackOutput output;

View File

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

View File

@ -57,7 +57,7 @@ import java.util.List;
/**
* @param output A {@link TrackOutput} to which H.264 samples should be written.
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
@ -420,7 +420,7 @@ import java.util.List;
}
private void outputSample(int offset) {
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
}

View File

@ -64,7 +64,7 @@ import java.util.Collections;
/**
* @param output A {@link TrackOutput} to which H.265 samples should be written.
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
*/
public H265Reader(TrackOutput output, SeiReader seiReader) {
super(output);
@ -471,7 +471,7 @@ import java.util.Collections;
}
private void outputSample(int offset) {
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
}

View File

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

View File

@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.text.eia608.Eia608Decoder;
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
/**
* Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}.
* Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
*/
/* package */ final class SeiReader {
@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
public SeiReader(TrackOutput output) {
this.output = output;
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, null,
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
Format.NO_VALUE, 0, null, null));
}
@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
payloadSize += b;
} while (b == 0xFF);
// Process the payload.
if (Eia608Decoder.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
// + user_data_type_code (1).
seiBuffer.skipBytes(8);
@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
seiBuffer.skipBytes(1);
int sampleBytes = 0;
for (int i = 0; i < ccCount; i++) {
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07;
// Check that validity == 1 and type == 0.
int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07;
// Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1).
if (ccValidityAndType != 0x04) {
seiBuffer.skipBytes(2);
seiBuffer.skipBytes(3);
} else {
sampleBytes += 2;
output.sampleData(seiBuffer, 2);
sampleBytes += 3;
output.sampleData(seiBuffer, 3);
}
}
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);

View File

@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -32,6 +30,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.Arrays;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
@ -50,30 +49,24 @@ public final class TsExtractor implements Extractor {
};
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
public static final int WORKAROUND_MAP_BY_TYPE = 16;
private static final String TAG = "TsExtractor";
private static final int TS_PACKET_SIZE = 188;
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
private static final int TS_PAT_PID = 0;
private static final int TS_STREAM_TYPE_MPA = 0x03;
private static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
private static final int TS_STREAM_TYPE_AAC = 0x0F;
private static final int TS_STREAM_TYPE_AC3 = 0x81;
private static final int TS_STREAM_TYPE_DTS = 0x8A;
private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
private static final int TS_STREAM_TYPE_E_AC3 = 0x87;
private static final int TS_STREAM_TYPE_H262 = 0x02;
private static final int TS_STREAM_TYPE_H264 = 0x1B;
private static final int TS_STREAM_TYPE_H265 = 0x24;
private static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1
public static final int TS_STREAM_TYPE_MPA = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC = 0x0F;
public static final int TS_STREAM_TYPE_AC3 = 0x81;
public static final int TS_STREAM_TYPE_DTS = 0x8A;
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
public static final int TS_STREAM_TYPE_H262 = 0x02;
public static final int TS_STREAM_TYPE_H264 = 0x1B;
public static final int TS_STREAM_TYPE_H265 = 0x24;
public static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
@ -83,35 +76,38 @@ public final class TsExtractor implements Extractor {
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final TimestampAdjuster timestampAdjuster;
private final int workaroundFlags;
private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters;
private final ElementaryStreamReader.Factory streamReaderFactory;
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
/* package */ final SparseBooleanArray trackIds;
// Accessed only by the loading thread.
private ExtractorOutput output;
private int nextEmbeddedTrackId;
/* package */ Id3Reader id3Reader;
public TsExtractor() {
this(new TimestampAdjuster(0));
}
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, 0);
this(timestampAdjuster, new DefaultStreamReaderFactory());
}
public TsExtractor(TimestampAdjuster timestampAdjuster, int workaroundFlags) {
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
* @param customReaderFactory Factory for injecting a custom set of elementary stream readers.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster,
ElementaryStreamReader.Factory customReaderFactory) {
this.timestampAdjuster = timestampAdjuster;
this.workaroundFlags = workaroundFlags;
this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
tsScratch = new ParsableBitArray(new byte[3]);
tsPayloadReaders = new SparseArray<>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
trackIds = new SparseBooleanArray();
nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
continuityCounters = new SparseIntArray();
}
@ -416,12 +412,6 @@ public final class TsExtractor implements Extractor {
// Skip the descriptors.
sectionData.skipBytes(programInfoLength);
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See b/20261500.
id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3));
}
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
- programInfoLength - 4 /* CRC length */;
while (remainingEntriesLength > 0) {
@ -431,63 +421,15 @@ public final class TsExtractor implements Extractor {
int elementaryPid = pmtScratch.readBits(13);
pmtScratch.skipBits(4); // reserved
int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
if (streamType == 0x06) {
streamType = esInfo.streamType;
}
remainingEntriesLength -= esInfoLength + 5;
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid;
if (trackIds.get(trackId)) {
continue;
}
ElementaryStreamReader pesPayloadReader;
switch (streamType) {
case TS_STREAM_TYPE_MPA:
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
break;
case TS_STREAM_TYPE_MPA_LSF:
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
break;
case TS_STREAM_TYPE_AAC:
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
break;
case TS_STREAM_TYPE_AC3:
case TS_STREAM_TYPE_E_AC3:
pesPayloadReader = new Ac3Reader(output.track(trackId), esInfo.language);
break;
case TS_STREAM_TYPE_DTS:
case TS_STREAM_TYPE_HDMV_DTS:
pesPayloadReader = new DtsReader(output.track(trackId), esInfo.language);
break;
case TS_STREAM_TYPE_H262:
pesPayloadReader = new H262Reader(output.track(trackId));
break;
case TS_STREAM_TYPE_H264:
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
: new H264Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)),
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
break;
case TS_STREAM_TYPE_H265:
pesPayloadReader = new H265Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)));
break;
case TS_STREAM_TYPE_ID3:
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
pesPayloadReader = id3Reader;
} else {
pesPayloadReader = new Id3Reader(output.track(nextEmbeddedTrackId++));
}
break;
default:
pesPayloadReader = null;
break;
}
ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid,
streamType, esInfo, output);
if (pesPayloadReader != null) {
trackIds.put(trackId, true);
tsPayloadReaders.put(elementaryPid,
new PesReader(pesPayloadReader, timestampAdjuster));
}
@ -497,18 +439,17 @@ public final class TsExtractor implements Extractor {
}
/**
* Returns the stream info read from the available descriptors, or -1 if no
* descriptors are present. Sets {@code data}'s position to the end of the descriptors.
* Returns the stream info read from the available descriptors. Sets {@code data}'s position to
* the end of the descriptors.
*
* @param data A buffer with its position set to the start of the first descriptor.
* @param length The length of descriptors to read from the current position in {@code data}.
* @return The stream info read from the available descriptors, or -1 if no
* descriptors are present.
* @return The stream info read from the available descriptors.
*/
private EsInfo readEsInfo(ParsableByteArray data, int length) {
int descriptorsEndPosition = data.getPosition() + length;
private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) {
int descriptorsStartPosition = data.getPosition();
int descriptorsEndPosition = descriptorsStartPosition + length;
int streamType = -1;
int audioType = -1;
String language = null;
while (data.getPosition() < descriptorsEndPosition) {
int descriptorTag = data.readUnsignedByte();
@ -531,27 +472,14 @@ public final class TsExtractor implements Extractor {
streamType = TS_STREAM_TYPE_DTS;
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
language = new String(data.data, data.getPosition(), 3).trim();
audioType = data.data[data.getPosition() + 3];
// Audio type is ignored.
}
// Skip unused bytes of current descriptor.
data.skipBytes(positionOfNextDescriptor - data.getPosition());
}
data.setPosition(descriptorsEndPosition);
return new EsInfo(streamType, audioType, language);
}
private final class EsInfo {
final int streamType;
final int audioType;
final String language;
public EsInfo(int streamType, int audioType, String language) {
this.streamType = streamType;
this.audioType = audioType;
this.language = language;
}
return new ElementaryStreamReader.EsInfo(streamType, language,
Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader {
/**
* Called when preparation completes.
* <p>
* May be called from any thread. After invoking this method, the {@link MediaPeriod} can expect
* Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
* called with the initial track selection.
*
@ -42,6 +42,17 @@ public interface MediaPeriod extends SequenceableLoader {
}
/**
* Prepares this media period asynchronously.
* <p>
* {@code callback.onPrepared} is called when preparation completes. If preparation fails,
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
*
* @param callback Callback to receive updates from this period, including being notified when
* preparation completes.
*/
void prepare(Callback callback);
/**
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such
* error exists.

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException;
@ -54,18 +53,13 @@ public interface MediaSource {
/**
* Returns a {@link MediaPeriod} corresponding to the period at the specified index.
* <p>
* {@link Callback#onPrepared(MediaPeriod)} is called when the new period is prepared. If
* preparation fails, {@link MediaPeriod#maybeThrowPrepareError()} will throw an
* {@link IOException} if called on the returned instance.
*
* @param index The index of the period.
* @param callback A callback to receive updates from the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The player's current playback position.
* @return A new {@link MediaPeriod}.
*/
MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, long positionUs);
MediaPeriod createPeriod(int index, Allocator allocator, long positionUs);
/**
* Releases the period.

View File

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

View File

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

View File

@ -29,7 +29,7 @@ public interface SequenceableLoader {
/**
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method
* to be called when it can continue to load data.
* to be called when it can continue to load data. Called on the playback thread.
*/
void onContinueLoadingRequested(T source);

View File

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

View File

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

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor;
@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
}

View File

@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
throw new IllegalStateException("Unexpected sample data in initialization chunk");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.source.hls;
import android.os.Handler;
import android.text.TextUtils;
import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -80,13 +82,15 @@ import java.util.LinkedList;
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks;
private final Runnable maybeFinishPrepareRunnable;
private final Handler handler;
private volatile boolean sampleQueuesBuilt;
private boolean sampleQueuesBuilt;
private boolean prepared;
private int enabledTrackCount;
private Format downstreamTrackFormat;
private int upstreamChunkUid;
private boolean released;
// Tracks are complicated in HLS. See documentation of buildTracks for details.
// Indexed by track (as exposed by this source).
@ -129,6 +133,13 @@ import java.util.LinkedList;
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>();
maybeFinishPrepareRunnable = new Runnable() {
@Override
public void run() {
maybeFinishPrepare();
}
};
handler = new Handler();
lastSeekPositionUs = positionUs;
pendingResetPositionUs = positionUs;
}
@ -137,6 +148,15 @@ import java.util.LinkedList;
continueLoading(lastSeekPositionUs);
}
/**
* Prepares a sample stream wrapper for which the master playlist provides enough information to
* prepare.
*/
public void prepareSingleTrack(Format format) {
track(0).format(format);
endTracks();
}
public void maybeThrowPrepareError() throws IOException {
maybeThrowError();
}
@ -245,6 +265,8 @@ import java.util.LinkedList;
sampleQueues.valueAt(i).disable();
}
loader.release();
handler.removeCallbacksAndMessages(null);
released = true;
}
public long getLargestQueuedTimestampUs() {
@ -454,7 +476,7 @@ import java.util.LinkedList;
@Override
public void endTracks() {
sampleQueuesBuilt = true;
maybeFinishPrepare();
handler.post(maybeFinishPrepareRunnable);
}
@Override
@ -462,17 +484,17 @@ import java.util.LinkedList;
// Do nothing.
}
// UpstreamFormatChangedListener implementation.
// UpstreamFormatChangedListener implementation. Called by the loading thread.
@Override
public void onUpstreamFormatChanged(Format format) {
maybeFinishPrepare();
handler.post(maybeFinishPrepareRunnable);
}
// Internal methods.
private void maybeFinishPrepare() {
if (prepared || !sampleQueuesBuilt) {
if (released || prepared || !sampleQueuesBuilt) {
return;
}
int sampleQueueCount = sampleQueues.size();
@ -558,7 +580,7 @@ import java.util.LinkedList;
if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
}
trackGroups[i] = new TrackGroup(formats);
primaryTrackGroupIndex = i;
@ -567,11 +589,11 @@ import java.util.LinkedList;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
trackFormat = muxedAudioFormat;
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
} else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) {
trackFormat = muxedCaptionFormat;
}
}
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
}
}
this.trackGroups = new TrackGroupArray(trackGroups);
@ -590,18 +612,25 @@ import java.util.LinkedList;
}
/**
* Derives a sample format corresponding to a given container format, by combining it with sample
* level information obtained from a second sample format.
* Derives a track format corresponding to a given container format, by combining it with sample
* level information obtained from the samples.
*
* @param containerFormat The container format for which the sample format should be derived.
* @param containerFormat The container format for which the track format should be derived.
* @param sampleFormat A sample format from which to obtain sample level information.
* @return The derived sample format.
* @return The derived track format.
*/
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
private static Format deriveFormat(Format containerFormat, Format sampleFormat) {
if (containerFormat == null) {
return sampleFormat;
}
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate,
String codecs = null;
int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType);
if (sampleTrackType == C.TRACK_TYPE_AUDIO) {
codecs = getAudioCodecs(containerFormat.codecs);
} else if (sampleTrackType == C.TRACK_TYPE_VIDEO) {
codecs = getVideoCodecs(containerFormat.codecs);
}
return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate,
containerFormat.width, containerFormat.height, containerFormat.selectionFlags,
containerFormat.language);
}
@ -614,4 +643,29 @@ import java.util.LinkedList;
return pendingResetPositionUs != C.TIME_UNSET;
}
private static String getAudioCodecs(String codecs) {
return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO);
}
private static String getVideoCodecs(String codecs) {
return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO);
}
private static String getCodecsOfType(String codecs, int trackType) {
if (TextUtils.isEmpty(codecs)) {
return null;
}
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
StringBuilder builder = new StringBuilder();
for (String codec : codecArray) {
if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) {
if (builder.length() > 0) {
builder.append(",");
}
builder.append(codec);
}
}
return builder.length() > 0 ? builder.toString() : null;
}
}

View File

@ -15,18 +15,29 @@
*/
package com.google.android.exoplayer2.source.hls.playlist;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Represents an HLS playlist.
*/
public abstract class HlsPlaylist {
/**
* The type of playlist.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_MASTER, TYPE_MEDIA})
public @interface Type {}
public static final int TYPE_MASTER = 0;
public static final int TYPE_MEDIA = 1;
public final String baseUri;
@Type
public final int type;
protected HlsPlaylist(String baseUri, int type) {
protected HlsPlaylist(String baseUri, @Type int type) {
this.baseUri = baseUri;
this.type = type;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,10 @@
*/
package com.google.android.exoplayer2.text;
import android.support.annotation.IntDef;
import android.text.Layout.Alignment;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains information about a specific cue, including textual content and formatting data.
@ -26,6 +29,13 @@ public class Cue {
* An unset position or width.
*/
public static final float DIMEN_UNSET = Float.MIN_VALUE;
/**
* The type of anchor, which may be unset.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
public @interface AnchorType {}
/**
* An unset anchor or line type value.
*/
@ -44,6 +54,13 @@ public class Cue {
* box.
*/
public static final int ANCHOR_TYPE_END = 2;
/**
* The type of line, which may be unset.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
public @interface LineType {}
/**
* Value for {@link #lineType} when {@link #line} is a fractional position.
*/
@ -83,6 +100,7 @@ public class Cue {
* -1). For horizontal text the size of the first line of the cue is its height, and the start
* and end of the viewport are the top and bottom respectively.
*/
@LineType
public final int lineType;
/**
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
@ -92,6 +110,7 @@ public class Cue {
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
* respectively.
*/
@AnchorType
public final int lineAnchor;
/**
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
@ -110,6 +129,7 @@ public class Cue {
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
* respectively.
*/
@AnchorType
public final int positionAnchor;
/**
* The size of the cue box in the writing direction specified as a fraction of the viewport size
@ -137,8 +157,8 @@ public class Cue {
* @param positionAnchor See {@link #positionAnchor}.
* @param size See {@link #size}.
*/
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor,
float position, int positionAnchor, float size) {
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
this.text = text;
this.textAlignment = textAlignment;
this.line = line;

View File

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

View File

@ -13,26 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.text.eia608;
package com.google.android.exoplayer2.text.cea;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.LinkedList;
import java.util.TreeSet;
/**
* A {@link SubtitleDecoder} for EIA-608 (also known as "line 21 captions" and "CEA-608").
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
*/
public final class Eia608Decoder implements SubtitleDecoder {
public final class Cea608Decoder extends CeaDecoder {
private static final int NUM_INPUT_BUFFERS = 10;
private static final int NUM_OUTPUT_BUFFERS = 2;
private static final int NTSC_CC_FIELD_1 = 0x00;
private static final int CC_VALID_FLAG = 0x04;
private static final int PAYLOAD_TYPE_CC = 4;
private static final int COUNTRY_CODE = 0xB5;
@ -159,18 +155,10 @@ public final class Eia608Decoder implements SubtitleDecoder {
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
};
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
private final ParsableByteArray ccData;
private final StringBuilder captionStringBuilder;
private long playbackPositionUs;
private SubtitleInputBuffer dequeuedInputBuffer;
private int captionMode;
private int captionRowCount;
private String captionString;
@ -181,17 +169,7 @@ public final class Eia608Decoder implements SubtitleDecoder {
private byte repeatableControlCc1;
private byte repeatableControlCc2;
public Eia608Decoder() {
availableInputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
availableInputBuffers.add(new SubtitleInputBuffer());
}
availableOutputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this));
}
queuedInputBuffers = new TreeSet<>();
public Cea608Decoder() {
ccData = new ParsableByteArray();
captionStringBuilder = new StringBuilder();
@ -202,101 +180,20 @@ public final class Eia608Decoder implements SubtitleDecoder {
@Override
public String getName() {
return "Eia608Decoder";
}
@Override
public void setPositionUs(long positionUs) {
playbackPositionUs = positionUs;
}
@Override
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
Assertions.checkState(dequeuedInputBuffer == null);
if (availableInputBuffers.isEmpty()) {
return null;
}
dequeuedInputBuffer = availableInputBuffers.pollFirst();
return dequeuedInputBuffer;
}
@Override
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
Assertions.checkArgument(inputBuffer != null);
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
queuedInputBuffers.add(inputBuffer);
dequeuedInputBuffer = null;
}
@Override
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
if (availableOutputBuffers.isEmpty()) {
return null;
}
// iterate through all available input buffers whose timestamps are less than or equal
// to the current playback position; processing input buffers for future content should
// be deferred until they would be applicable
while (!queuedInputBuffers.isEmpty()
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
// If the input buffer indicates we've reached the end of the stream, we can
// return immediately with an output buffer propagating that
if (inputBuffer.isEndOfStream()) {
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
decode(inputBuffer);
// check if we have any caption updates to report
if (!TextUtils.equals(captionString, lastCaptionString)) {
lastCaptionString = captionString;
if (!inputBuffer.isDecodeOnly()) {
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
outputBuffer.setContent(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
}
releaseInputBuffer(inputBuffer);
}
return null;
}
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
inputBuffer.clear();
availableInputBuffers.add(inputBuffer);
}
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
outputBuffer.clear();
availableOutputBuffers.add(outputBuffer);
return "Cea608Decoder";
}
@Override
public void flush() {
super.flush();
setCaptionMode(CC_MODE_UNKNOWN);
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
playbackPositionUs = 0;
captionStringBuilder.setLength(0);
captionString = null;
lastCaptionString = null;
repeatableControlSet = false;
repeatableControlCc1 = 0;
repeatableControlCc2 = 0;
while (!queuedInputBuffers.isEmpty()) {
releaseInputBuffer(queuedInputBuffers.pollFirst());
}
if (dequeuedInputBuffer != null) {
releaseInputBuffer(dequeuedInputBuffer);
dequeuedInputBuffer = null;
}
}
@Override
@ -304,14 +201,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
// Do nothing
}
private void decode(SubtitleInputBuffer inputBuffer) {
@Override
protected boolean isNewSubtitleDataAvailable() {
return !TextUtils.equals(captionString, lastCaptionString);
}
@Override
protected Subtitle createSubtitle() {
lastCaptionString = captionString;
return new CeaSubtitle(new Cue(captionString));
}
@Override
protected void decode(SubtitleInputBuffer inputBuffer) {
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
boolean captionDataProcessed = false;
boolean isRepeatableControl = false;
while (ccData.bytesLeft() > 0) {
byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07);
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
// Only examine valid NTSC_CC_FIELD_1 packets
if (ccTypeAndValid != (CC_VALID_FLAG | NTSC_CC_FIELD_1)) {
// TODO: Add support for NTSC_CC_FIELD_2 packets
continue;
}
// Ignore empty captions.
if (ccData1 == 0 && ccData2 == 0) {
continue;
@ -320,26 +236,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
captionDataProcessed = true;
// Special North American character set.
// ccData1 - P|0|0|1|C|0|0|1
// ccData2 - P|0|1|1|X|X|X|X
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
// TODO: Make use of the channel bit
captionStringBuilder.append(getSpecialChar(ccData2));
continue;
}
// Extended Spanish/Miscellaneous and French character set.
// Extended Western European character set.
// ccData1 - P|0|0|1|C|0|1|S
// ccData2 - P|0|1|X|X|X|X|X
if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
continue;
}
if ((ccData2 & 0x60) == 0x20) {
// Extended Spanish/Miscellaneous and French character set (S = 0).
if (ccData1 == 0x12 || ccData1 == 0x1A) {
// TODO: Make use of the channel bit
backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
continue;
}
// Extended Portuguese and German/Danish character set.
// ccData2 - P|0|1|X|X|X|X|X
if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
continue;
// Extended Portuguese and German/Danish character set (S = 1).
if (ccData1 == 0x13 || ccData1 == 0x1B) {
// TODO: Make use of the channel bit
backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
continue;
}
}
// Control character.
@ -367,15 +290,17 @@ public final class Eia608Decoder implements SubtitleDecoder {
private boolean handleCtrl(byte cc1, byte cc2) {
boolean isRepeatableControl = isRepeatable(cc1);
if (isRepeatableControl && repeatableControlSet
&& repeatableControlCc1 == cc1
&& repeatableControlCc2 == cc2) {
repeatableControlSet = false;
return true;
} else if (isRepeatableControl) {
repeatableControlSet = true;
repeatableControlCc1 = cc1;
repeatableControlCc2 = cc2;
if (isRepeatableControl) {
if (repeatableControlSet
&& repeatableControlCc1 == cc1
&& repeatableControlCc2 == cc2) {
repeatableControlSet = false;
return true;
} else {
repeatableControlSet = true;
repeatableControlCc1 = cc1;
repeatableControlCc2 = cc2;
}
}
if (isMiscCode(cc1, cc2)) {
handleMiscCode(cc2);
@ -526,16 +451,16 @@ public final class Eia608Decoder implements SubtitleDecoder {
}
/**
* Inspects an sei message to determine whether it contains EIA-608.
* Inspects an sei message to determine whether it contains CEA-608.
* <p>
* The position of {@code payload} is left unchanged.
*
* @param payloadType The payload type of the message.
* @param payloadLength The length of the payload.
* @param payload A {@link ParsableByteArray} containing the payload.
* @return Whether the sei message contains EIA-608.
* @return Whether the sei message contains CEA-608.
*/
public static boolean isSeiMessageEia608(int payloadType, int payloadLength,
public static boolean isSeiMessageCea608(int payloadType, int payloadLength,
ParsableByteArray payload) {
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
return false;

View File

@ -0,0 +1,167 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.text.cea;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import java.util.LinkedList;
import java.util.TreeSet;
/**
* Base class for subtitle parsers for CEA captions.
*/
/* package */ abstract class CeaDecoder implements SubtitleDecoder {
private static final int NUM_INPUT_BUFFERS = 10;
private static final int NUM_OUTPUT_BUFFERS = 2;
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
private SubtitleInputBuffer dequeuedInputBuffer;
private long playbackPositionUs;
public CeaDecoder() {
availableInputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
availableInputBuffers.add(new SubtitleInputBuffer());
}
availableOutputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
availableOutputBuffers.add(new CeaOutputBuffer(this));
}
queuedInputBuffers = new TreeSet<>();
}
@Override
public abstract String getName();
@Override
public void setPositionUs(long positionUs) {
playbackPositionUs = positionUs;
}
@Override
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
Assertions.checkState(dequeuedInputBuffer == null);
if (availableInputBuffers.isEmpty()) {
return null;
}
dequeuedInputBuffer = availableInputBuffers.pollFirst();
return dequeuedInputBuffer;
}
@Override
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
Assertions.checkArgument(inputBuffer != null);
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
queuedInputBuffers.add(inputBuffer);
dequeuedInputBuffer = null;
}
@Override
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
if (availableOutputBuffers.isEmpty()) {
return null;
}
// iterate through all available input buffers whose timestamps are less than or equal
// to the current playback position; processing input buffers for future content should
// be deferred until they would be applicable
while (!queuedInputBuffers.isEmpty()
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
// If the input buffer indicates we've reached the end of the stream, we can
// return immediately with an output buffer propagating that
if (inputBuffer.isEndOfStream()) {
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
decode(inputBuffer);
// check if we have any caption updates to report
if (isNewSubtitleDataAvailable()) {
// Even if the subtitle is decode-only; we need to generate it to consume the data so it
// isn't accidentally prepended to the next subtitle
Subtitle subtitle = createSubtitle();
if (!inputBuffer.isDecodeOnly()) {
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
outputBuffer.setContent(inputBuffer.timeUs, subtitle, 0);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
}
releaseInputBuffer(inputBuffer);
}
return null;
}
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
inputBuffer.clear();
availableInputBuffers.add(inputBuffer);
}
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
outputBuffer.clear();
availableOutputBuffers.add(outputBuffer);
}
@Override
public void flush() {
playbackPositionUs = 0;
while (!queuedInputBuffers.isEmpty()) {
releaseInputBuffer(queuedInputBuffers.pollFirst());
}
if (dequeuedInputBuffer != null) {
releaseInputBuffer(dequeuedInputBuffer);
dequeuedInputBuffer = null;
}
}
@Override
public void release() {
// Do nothing
}
/**
* Returns whether there is data available to create a new {@link Subtitle}.
*/
protected abstract boolean isNewSubtitleDataAvailable();
/**
* Creates a {@link Subtitle} from the available data.
*/
protected abstract Subtitle createSubtitle();
/**
* Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()}
* when sufficient data has been processed.
*/
protected abstract void decode(SubtitleInputBuffer inputBuffer);
}

View File

@ -13,22 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.text.eia608;
package com.google.android.exoplayer2.text.cea;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
/**
* A {@link Subtitle} output from an {@link Eia608Decoder}.
* A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s.
*/
/* package */ final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer {
public final class CeaOutputBuffer extends SubtitleOutputBuffer {
private Eia608Decoder owner;
private final CeaDecoder owner;
/**
* @param owner The decoder that owns this buffer.
*/
public Eia608SubtitleOutputBuffer(Eia608Decoder owner) {
public CeaOutputBuffer(CeaDecoder owner) {
super();
this.owner = owner;
}

View File

@ -13,26 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.text.eia608;
package com.google.android.exoplayer2.text.cea;
import android.text.TextUtils;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle;
import java.util.Collections;
import java.util.List;
/**
* A representation of an EIA-608 subtitle.
* A representation of a CEA subtitle.
*/
/* package */ final class Eia608Subtitle implements Subtitle {
/* package */ final class CeaSubtitle implements Subtitle {
private final String text;
private final List<Cue> cues;
/**
* @param text The subtitle text.
* @param cue The subtitle cue.
*/
public Eia608Subtitle(String text) {
this.text = text;
public CeaSubtitle(Cue cue) {
if (cue == null) {
cues = Collections.emptyList();
} else {
cues = Collections.singletonList(cue);
}
}
@Override
@ -52,11 +55,8 @@ import java.util.List;
@Override
public List<Cue> getCues(long timeUs) {
if (TextUtils.isEmpty(text)) {
return Collections.emptyList();
} else {
return Collections.singletonList(new Cue(text));
}
return cues;
}
}

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.text.Cue;
public final float position;
public final float line;
@Cue.LineType
public final int lineType;
public final float width;
@ -31,7 +32,7 @@ import com.google.android.exoplayer2.text.Cue;
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
}
public TtmlRegion(float position, float line, int lineType, float width) {
public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) {
this.position = position;
this.line = line;
this.lineType = lineType;

View File

@ -16,8 +16,11 @@
package com.google.android.exoplayer2.text.ttml;
import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.text.Layout;
import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Style object of a <code>TtmlNode</code>
@ -26,15 +29,25 @@ import com.google.android.exoplayer2.util.Assertions;
public static final int UNSPECIFIED = -1;
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
STYLE_BOLD_ITALIC})
public @interface StyleFlags {}
public static final int STYLE_NORMAL = Typeface.NORMAL;
public static final int STYLE_BOLD = Typeface.BOLD;
public static final int STYLE_ITALIC = Typeface.ITALIC;
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
public @interface FontSizeUnit {}
public static final int FONT_SIZE_UNIT_PIXEL = 1;
public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, OFF, ON})
private @interface OptionalBoolean {}
private static final int OFF = 0;
private static final int ON = 1;
@ -43,10 +56,15 @@ import com.google.android.exoplayer2.util.Assertions;
private boolean hasFontColor;
private int backgroundColor;
private boolean hasBackgroundColor;
@OptionalBoolean
private int linethrough;
@OptionalBoolean
private int underline;
@OptionalBoolean
private int bold;
@OptionalBoolean
private int italic;
@FontSizeUnit
private int fontSizeUnit;
private float fontSize;
private String id;
@ -67,12 +85,13 @@ import com.google.android.exoplayer2.util.Assertions;
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
* or {@link #STYLE_BOLD_ITALIC}.
*/
@StyleFlags
public int getStyle() {
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
return UNSPECIFIED;
}
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
| (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
}
public boolean isLinethrough() {
@ -95,6 +114,18 @@ import com.google.android.exoplayer2.util.Assertions;
return this;
}
public TtmlStyle setBold(boolean bold) {
Assertions.checkState(inheritableStyle == null);
this.bold = bold ? ON : OFF;
return this;
}
public TtmlStyle setItalic(boolean italic) {
Assertions.checkState(inheritableStyle == null);
this.italic = italic ? ON : OFF;
return this;
}
public String getFontFamily() {
return fontFamily;
}
@ -140,18 +171,6 @@ import com.google.android.exoplayer2.util.Assertions;
return hasBackgroundColor;
}
public TtmlStyle setBold(boolean isBold) {
Assertions.checkState(inheritableStyle == null);
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
return this;
}
public TtmlStyle setItalic(boolean isItalic) {
Assertions.checkState(inheritableStyle == null);
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
return this;
}
/**
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
* are not inheritable are not inherited as well as properties which are already set locally
@ -236,6 +255,7 @@ import com.google.android.exoplayer2.util.Assertions;
return this;
}
@FontSizeUnit
public int getFontSizeUnit() {
return fontSizeUnit;
}

View File

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

View File

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

View File

@ -18,22 +18,301 @@ package com.google.android.exoplayer2.trackselection;
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* A {@link MappingTrackSelector} that allows configuration of common parameters.
* A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call
* the methods of this class from the application thread. See {@link Parameters#Parameters()} for
* default selection parameters.
*/
public class DefaultTrackSelector extends MappingTrackSelector {
/**
* Holder for available configurations for the {@link DefaultTrackSelector}.
*/
public static final class Parameters {
// Audio.
public final String preferredAudioLanguage;
// Text.
public final String preferredTextLanguage;
// Video.
public final boolean allowMixedMimeAdaptiveness;
public final boolean allowNonSeamlessAdaptiveness;
public final int maxVideoWidth;
public final int maxVideoHeight;
public final boolean exceedVideoConstraintsIfNecessary;
public final int viewportWidth;
public final int viewportHeight;
public final boolean orientationMayChange;
/**
* Constructor with default selection parameters:
* <ul>
* <li>No preferred audio language is set.</li>
* <li>No preferred text language is set.</li>
* <li>Adaptation between different mime types is not allowed.</li>
* <li>Non seamless adaptation is allowed.</li>
* <li>No max limit for video width/height.</li>
* <li>Video constraints are ignored if no supported selection can be made otherwise.</li>
* <li>No viewport width/height constraints are set.</li>
* </ul>
*/
public Parameters() {
this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE,
Integer.MAX_VALUE, true);
}
/**
* @param preferredAudioLanguage The preferred language for audio, as well as for forced text
* tracks as defined by RFC 5646. {@code null} to select the default track, or first track
* if there's no default.
* @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646.
* {@code null} to select the default track, or first track if there's no default.
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
* @param maxVideoWidth Maximum allowed video width.
* @param maxVideoHeight Maximum allowed video height.
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
* can be made otherwise. False to force constraints anyway.
* @param viewportWidth Viewport width in pixels.
* @param viewportHeight Viewport height in pixels.
* @param orientationMayChange Whether orientation may change during playback.
*/
public Parameters(String preferredAudioLanguage, String preferredTextLanguage,
boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness,
int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary,
int viewportWidth, int viewportHeight, boolean orientationMayChange) {
this.preferredAudioLanguage = preferredAudioLanguage;
this.preferredTextLanguage = preferredTextLanguage;
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
this.maxVideoWidth = maxVideoWidth;
this.maxVideoHeight = maxVideoHeight;
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.orientationMayChange = orientationMayChange;
}
/**
* Returns a {@link Parameters} instance with the provided preferred language for audio and
* forced text tracks.
*
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
* select the default track, or first track if there's no default.
* @return A {@link Parameters} instance with the provided preferred language for audio and
* forced text tracks.
*/
public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) {
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight,
exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange);
}
/**
* Returns a {@link Parameters} instance with the provided preferred language for text tracks.
*
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
* select the default track, or no track if there's no default.
* @return A {@link Parameters} instance with the provided preferred language for text tracks.
*/
public Parameters withPreferredTextLanguage(String preferredTextLanguage) {
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
*
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
* @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
*/
public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance.
*
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
* @return A {@link Parameters} instance with the provided seamless adaptiveness allowance.
*/
public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Returns a {@link Parameters} instance with the provided max video size.
*
* @param maxVideoWidth The max video width.
* @param maxVideoHeight The max video width.
* @return A {@link Parameters} instance with the provided max video size.
*/
public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Equivalent to {@code withMaxVideoSize(1279, 719)}.
*
* @return A {@link Parameters} instance with maximum standard definition as maximum video size.
*/
public Parameters withMaxVideoSizeSd() {
return withMaxVideoSize(1279, 719);
}
/**
* Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
*
* @return A {@link Parameters} instance without video size constraints.
*/
public Parameters withoutVideoSizeConstraints() {
return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Returns a {@link Parameters} instance with the provided
* {@code exceedVideoConstraintsIfNecessary} value.
*
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
* can be made otherwise. False to force constraints anyway.
* @return A {@link Parameters} instance with the provided
* {@code exceedVideoConstraintsIfNecessary} value.
*/
public Parameters withExceedVideoConstraintsIfNecessary(
boolean exceedVideoConstraintsIfNecessary) {
if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Returns a {@link Parameters} instance with the provided viewport size.
*
* @param viewportWidth Viewport width in pixels.
* @param viewportHeight Viewport height in pixels.
* @param orientationMayChange Whether orientation may change during playback.
* @return A {@link Parameters} instance with the provided viewport size.
*/
public Parameters withViewportSize(int viewportWidth, int viewportHeight,
boolean orientationMayChange) {
if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight
&& orientationMayChange == this.orientationMayChange) {
return this;
}
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
}
/**
* Returns a {@link Parameters} instance where the viewport size is obtained from the provided
* {@link Context}.
*
* @param context The context to obtain the viewport size from.
* @param orientationMayChange Whether orientation may change during playback.
* @return A {@link Parameters} instance where the viewport size is obtained from the provided
* {@link Context}.
*/
public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) {
// Assume the viewport is fullscreen.
Point viewportSize = Util.getPhysicalDisplaySize(context);
return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
}
/**
* Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
*
* @return A {@link Parameters} instance without viewport size constraints.
*/
public Parameters withoutViewportSizeConstraints() {
return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Parameters other = (Parameters) obj;
return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness
&& allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness
&& maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight
&& exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary
&& orientationMayChange == other.orientationMayChange
&& viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
&& TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage)
&& TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage);
}
@Override
public int hashCode() {
int result = preferredAudioLanguage.hashCode();
result = 31 * result + preferredTextLanguage.hashCode();
result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0);
result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0);
result = 31 * result + maxVideoWidth;
result = 31 * result + maxVideoHeight;
result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0);
result = 31 * result + (orientationMayChange ? 1 : 0);
result = 31 * result + viewportWidth;
result = 31 * result + viewportHeight;
return result;
}
}
/**
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
@ -43,22 +322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static final int[] NO_TRACKS = new int[0];
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
// Audio.
private String preferredAudioLanguage;
// Text.
private String preferredTextLanguage;
// Video.
private boolean allowMixedMimeAdaptiveness;
private boolean allowNonSeamlessAdaptiveness;
private int maxVideoWidth;
private int maxVideoHeight;
private boolean exceedVideoConstraintsIfNecessary;
private boolean orientationMayChange;
private int viewportWidth;
private int viewportHeight;
private final AtomicReference<Parameters> params;
/**
* Constructs an instance that does not support adaptive video.
@ -82,142 +346,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
super(eventHandler);
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
allowNonSeamlessAdaptiveness = true;
exceedVideoConstraintsIfNecessary = true;
maxVideoWidth = Integer.MAX_VALUE;
maxVideoHeight = Integer.MAX_VALUE;
viewportWidth = Integer.MAX_VALUE;
viewportHeight = Integer.MAX_VALUE;
orientationMayChange = true;
params = new AtomicReference<>(new Parameters());
}
/**
* Sets the preferred language for audio, as well as for forced text tracks.
* Atomically sets the provided parameters for track selection.
*
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
* select the default track, or first track if there's no default.
* @param params The parameters for track selection.
*/
public void setPreferredLanguages(String preferredAudioLanguage) {
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) {
this.preferredAudioLanguage = preferredAudioLanguage;
public void setParameters(Parameters params) {
if (!this.params.get().equals(params)) {
this.params.set(Assertions.checkNotNull(params));
invalidate();
}
}
/**
* Sets the preferred language for text tracks.
* Gets the current selection parameters.
*
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
* select the default track, or no track if there's no default.
* @return The current selection parameters.
*/
public void setPreferredTextLanguage(String preferredTextLanguage) {
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
if (!Util.areEqual(this.preferredTextLanguage, preferredTextLanguage)) {
this.preferredTextLanguage = preferredTextLanguage;
invalidate();
}
}
/**
* Sets whether to allow selections to contain mixed mime types.
*
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
*/
public void allowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
if (this.allowMixedMimeAdaptiveness != allowMixedMimeAdaptiveness) {
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
invalidate();
}
}
/**
* Sets whether non-seamless adaptation is allowed.
*
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
*/
public void allowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
if (this.allowNonSeamlessAdaptiveness != allowNonSeamlessAdaptiveness) {
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
invalidate();
}
}
/**
* Sets the maximum allowed size for video tracks.
*
* @param maxVideoWidth Maximum allowed width.
* @param maxVideoHeight Maximum allowed height.
*/
public void setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
if (this.maxVideoWidth != maxVideoWidth || this.maxVideoHeight != maxVideoHeight) {
this.maxVideoWidth = maxVideoWidth;
this.maxVideoHeight = maxVideoHeight;
invalidate();
}
}
/**
* Equivalent to {@code setMaxVideoSize(1279, 719)}.
*/
public void setMaxVideoSizeSd() {
setMaxVideoSize(1279, 719);
}
/**
* Equivalent to {@code setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
*/
public void clearMaxVideoSize() {
setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Sets whether video constraints should be ignored when no selection can be made otherwise.
*
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
* can be made otherwise. False to force constraints anyway.
*/
public void setExceedVideoConstraintsIfNecessary(boolean exceedVideoConstraintsIfNecessary) {
if (this.exceedVideoConstraintsIfNecessary != exceedVideoConstraintsIfNecessary) {
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
invalidate();
}
}
/**
* Sets the target viewport size for selecting video tracks.
*
* @param viewportWidth Viewport width in pixels.
* @param viewportHeight Viewport height in pixels.
* @param orientationMayChange Whether orientation may change during playback.
*/
public void setViewportSize(int viewportWidth, int viewportHeight, boolean orientationMayChange) {
if (this.viewportWidth != viewportWidth || this.viewportHeight != viewportHeight
|| this.orientationMayChange != orientationMayChange) {
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.orientationMayChange = orientationMayChange;
invalidate();
}
}
/**
* Retrieves the viewport size from the provided {@link Context} and calls
* {@link #setViewportSize(int, int, boolean)} with this information.
*
* @param context The context to obtain the viewport size from.
* @param orientationMayChange Whether orientation may change during playback.
*/
public void setViewportSizeFromContext(Context context, boolean orientationMayChange) {
Point viewportSize = Util.getPhysicalDisplaySize(context); // Assume the viewport is fullscreen.
setViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
}
/**
* Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
*/
public void clearViewportConstraints() {
setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
public Parameters getParameters() {
return params.get();
}
// MappingTrackSelector implementation.
@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector {
throws ExoPlaybackException {
// Make a track selection for each renderer.
TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length];
Parameters params = this.params.get();
for (int i = 0; i < rendererCapabilities.length; i++) {
switch (rendererCapabilities[i].getTrackType()) {
case C.TRACK_TYPE_VIDEO:
rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i],
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight,
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth,
viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory,
exceedVideoConstraintsIfNecessary);
rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth,
params.maxVideoHeight, params.allowNonSeamlessAdaptiveness,
params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight,
params.orientationMayChange, adaptiveVideoTrackSelectionFactory,
params.exceedVideoConstraintsIfNecessary);
break;
case C.TRACK_TYPE_AUDIO:
rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i],
rendererFormatSupports[i], preferredAudioLanguage);
rendererFormatSupports[i], params.preferredAudioLanguage);
break;
case C.TRACK_TYPE_TEXT:
rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i],
rendererFormatSupports[i], preferredTextLanguage, preferredAudioLanguage);
rendererFormatSupports[i], params.preferredTextLanguage,
params.preferredAudioLanguage);
break;
default:
rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(),
@ -442,7 +595,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(trackIndex);
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
int trackScore;
if (formatHasLanguage(format, preferredAudioLanguage)) {
if (isDefault) {
@ -480,8 +633,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(trackIndex);
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
boolean isForced = (format.selectionFlags & Format.SELECTION_FLAG_FORCED) != 0;
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0;
int trackScore;
if (formatHasLanguage(format, preferredTextLanguage)) {
if (isDefault) {
@ -530,7 +683,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) {
Format format = trackGroup.getFormat(trackIndex);
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
int trackScore = isDefault ? 2 : 1;
if (trackScore > selectedTrackScore) {
selectedGroup = trackGroup;

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