mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
commit
f8a8302f7b
13
README.md
13
README.md
@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates.
|
|||||||
|
|
||||||
#### Via jCenter ####
|
#### Via jCenter ####
|
||||||
|
|
||||||
The easiest way to get started using ExoPlayer is by including the following in
|
The easiest way to get started using ExoPlayer is to add it as a gradle
|
||||||
your project's `build.gradle` file:
|
dependency. You need to make sure you have the jcenter repository included in
|
||||||
|
the `build.gradle` file in the root of your project:
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, include the following in your module's `build.gradle` file:
|
||||||
|
|
||||||
```gradle
|
```gradle
|
||||||
compile 'com.google.android.exoplayer:exoplayer:rX.X.X'
|
compile 'com.google.android.exoplayer:exoplayer:rX.X.X'
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### r2.0.1 ###
|
||||||
|
|
||||||
|
* Fix playback of short duration content
|
||||||
|
([#1837](https://github.com/google/ExoPlayer/issues/1837)).
|
||||||
|
* Fix MergingMediaSource preparation issue
|
||||||
|
([#1853](https://github.com/google/ExoPlayer/issues/1853)).
|
||||||
|
* Fix live stream buffering (out of memory) issue
|
||||||
|
([#1825](https://github.com/google/ExoPlayer/issues/1825)).
|
||||||
|
|
||||||
### r2.0.0 ###
|
### r2.0.0 ###
|
||||||
|
|
||||||
ExoPlayer 2.x is a major iteration of the library. It includes significant API
|
ExoPlayer 2.x is a major iteration of the library. It includes significant API
|
||||||
|
@ -39,14 +39,14 @@ android {
|
|||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
demo
|
demo
|
||||||
demo_ext
|
demoExt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':library')
|
compile project(':library')
|
||||||
demo_extCompile project(path: ':extension-ffmpeg')
|
demoExtCompile project(path: ':extension-ffmpeg')
|
||||||
demo_extCompile project(path: ':extension-flac')
|
demoExtCompile project(path: ':extension-flac')
|
||||||
demo_extCompile project(path: ':extension-opus')
|
demoExtCompile project(path: ':extension-opus')
|
||||||
demo_extCompile project(path: ':extension-vp9')
|
demoExtCompile project(path: ':extension-vp9')
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.google.android.exoplayer2.demo"
|
package="com.google.android.exoplayer2.demo"
|
||||||
android:versionCode="2000"
|
android:versionCode="2001"
|
||||||
android:versionName="2.0.0">
|
android:versionName="2.0.1">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
@ -16,9 +16,33 @@
|
|||||||
package com.google.android.exoplayer2.demo;
|
package com.google.android.exoplayer2.demo;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder application to facilitate overriding Application methods for debugging and testing.
|
* Placeholder application to facilitate overriding Application methods for debugging and testing.
|
||||||
*/
|
*/
|
||||||
public class DemoApplication extends Application {
|
public class DemoApplication extends Application {
|
||||||
|
|
||||||
|
protected String userAgent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||||
|
return new DefaultDataSourceFactory(this, bandwidthMeter,
|
||||||
|
buildHttpDataSourceFactory(bandwidthMeter));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||||
|
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
|||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -54,7 +55,7 @@ import java.util.Locale;
|
|||||||
/* package */ final class EventLogger implements ExoPlayer.EventListener,
|
/* package */ final class EventLogger implements ExoPlayer.EventListener,
|
||||||
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
|
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
|
||||||
ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener,
|
ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener,
|
||||||
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> {
|
TrackSelector.EventListener<MappedTrackInfo>, MetadataRenderer.Output<List<Id3Frame>> {
|
||||||
|
|
||||||
private static final String TAG = "EventLogger";
|
private static final String TAG = "EventLogger";
|
||||||
private static final int MAX_TIMELINE_ITEM_LINES = 3;
|
private static final int MAX_TIMELINE_ITEM_LINES = 3;
|
||||||
@ -125,23 +126,24 @@ import java.util.Locale;
|
|||||||
// MappingTrackSelector.EventListener
|
// MappingTrackSelector.EventListener
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksChanged(TrackInfo trackInfo) {
|
public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
|
||||||
Log.d(TAG, "Tracks [");
|
Log.d(TAG, "Tracks [");
|
||||||
// Log tracks associated to renderers.
|
// Log tracks associated to renderers.
|
||||||
for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) {
|
MappedTrackInfo info = trackSelections.info;
|
||||||
TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex);
|
for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) {
|
||||||
TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex);
|
TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex);
|
||||||
|
TrackSelection trackSelection = trackSelections.get(rendererIndex);
|
||||||
if (trackGroups.length > 0) {
|
if (trackGroups.length > 0) {
|
||||||
Log.d(TAG, " Renderer:" + rendererIndex + " [");
|
Log.d(TAG, " Renderer:" + rendererIndex + " [");
|
||||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||||
TrackGroup trackGroup = trackGroups.get(groupIndex);
|
TrackGroup trackGroup = trackGroups.get(groupIndex);
|
||||||
String adaptiveSupport = getAdaptiveSupportString(
|
String adaptiveSupport = getAdaptiveSupportString(
|
||||||
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
|
trackGroup.length, info.getAdaptiveSupport(rendererIndex, groupIndex, false));
|
||||||
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
|
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
|
||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||||
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
|
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
|
||||||
String formatSupport = getFormatSupportString(
|
String formatSupport = getFormatSupportString(
|
||||||
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
|
info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
|
||||||
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
|
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
|
||||||
+ getFormatString(trackGroup.getFormat(trackIndex))
|
+ getFormatString(trackGroup.getFormat(trackIndex))
|
||||||
+ ", supported=" + formatSupport);
|
+ ", supported=" + formatSupport);
|
||||||
@ -152,7 +154,7 @@ import java.util.Locale;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Log tracks not associated with a renderer.
|
// Log tracks not associated with a renderer.
|
||||||
TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups();
|
TrackGroupArray trackGroups = info.getUnassociatedTrackGroups();
|
||||||
if (trackGroups.length > 0) {
|
if (trackGroups.length > 0) {
|
||||||
Log.d(TAG, " Renderer:None [");
|
Log.d(TAG, " Renderer:None [");
|
||||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||||
|
@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
|||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
@ -55,15 +56,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
|||||||
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.ui.DebugTextViewHelper;
|
import com.google.android.exoplayer2.ui.DebugTextViewHelper;
|
||||||
import com.google.android.exoplayer2.ui.PlaybackControlView;
|
import com.google.android.exoplayer2.ui.PlaybackControlView;
|
||||||
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
@ -77,7 +78,7 @@ import java.util.UUID;
|
|||||||
* An activity that plays media using {@link SimpleExoPlayer}.
|
* An activity that plays media using {@link SimpleExoPlayer}.
|
||||||
*/
|
*/
|
||||||
public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener,
|
public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener,
|
||||||
MappingTrackSelector.EventListener, PlaybackControlView.VisibilityListener {
|
TrackSelector.EventListener<MappedTrackInfo>, PlaybackControlView.VisibilityListener {
|
||||||
|
|
||||||
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
||||||
public static final String DRM_LICENSE_URL = "drm_license_url";
|
public static final String DRM_LICENSE_URL = "drm_license_url";
|
||||||
@ -106,7 +107,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
private TextView debugTextView;
|
private TextView debugTextView;
|
||||||
private Button retryButton;
|
private Button retryButton;
|
||||||
|
|
||||||
private String userAgent;
|
|
||||||
private DataSource.Factory mediaDataSourceFactory;
|
private DataSource.Factory mediaDataSourceFactory;
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
private MappingTrackSelector trackSelector;
|
private MappingTrackSelector trackSelector;
|
||||||
@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
shouldAutoPlay = true;
|
shouldAutoPlay = true;
|
||||||
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
|
||||||
mediaDataSourceFactory = buildDataSourceFactory(true);
|
mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||||
mainHandler = new Handler();
|
mainHandler = new Handler();
|
||||||
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
||||||
@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
initializePlayer();
|
initializePlayer();
|
||||||
} else if (view.getParent() == debugRootView) {
|
} else if (view.getParent() == debugRootView) {
|
||||||
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
|
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
|
||||||
trackSelector.getTrackInfo(), (int) view.getTag());
|
trackSelector.getCurrentSelections().info, (int) view.getTag());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +221,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
||||||
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
||||||
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
|
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
|
||||||
DrmSessionManager drmSessionManager = null;
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||||
if (drmSchemeUuid != null) {
|
if (drmSchemeUuid != null) {
|
||||||
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
|
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
|
||||||
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
|
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
|
||||||
@ -316,15 +315,15 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||||
: uri.getLastPathSegment());
|
: uri.getLastPathSegment());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Util.TYPE_SS:
|
case C.TYPE_SS:
|
||||||
return new SsMediaSource(uri, buildDataSourceFactory(false),
|
return new SsMediaSource(uri, buildDataSourceFactory(false),
|
||||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
||||||
case Util.TYPE_DASH:
|
case C.TYPE_DASH:
|
||||||
return new DashMediaSource(uri, buildDataSourceFactory(false),
|
return new DashMediaSource(uri, buildDataSourceFactory(false),
|
||||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
|
||||||
case Util.TYPE_HLS:
|
case C.TYPE_HLS:
|
||||||
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
|
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
|
||||||
case Util.TYPE_OTHER:
|
case C.TYPE_OTHER:
|
||||||
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
|
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
|
||||||
mainHandler, eventLogger);
|
mainHandler, eventLogger);
|
||||||
default: {
|
default: {
|
||||||
@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl,
|
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
|
||||||
Map<String, String> keyRequestProperties)
|
String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
|
||||||
throws UnsupportedDrmException {
|
|
||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
* @return A new DataSource factory.
|
* @return A new DataSource factory.
|
||||||
*/
|
*/
|
||||||
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
||||||
return new DefaultDataSourceFactory(this, useBandwidthMeter ? BANDWIDTH_METER : null,
|
return ((DemoApplication) getApplication())
|
||||||
buildHttpDataSourceFactory(useBandwidthMeter));
|
.buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
* @return A new HttpDataSource factory.
|
* @return A new HttpDataSource factory.
|
||||||
*/
|
*/
|
||||||
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
||||||
return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null);
|
return ((DemoApplication) getApplication())
|
||||||
|
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExoPlayer.EventListener implementation
|
// ExoPlayer.EventListener implementation
|
||||||
@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
// MappingTrackSelector.EventListener implementation
|
// MappingTrackSelector.EventListener implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksChanged(TrackInfo trackInfo) {
|
public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
|
||||||
updateButtonVisibilities();
|
updateButtonVisibilities();
|
||||||
|
MappedTrackInfo trackInfo = trackSelections.info;
|
||||||
if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) {
|
if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) {
|
||||||
showToast(R.string.error_unsupported_video);
|
showToast(R.string.error_unsupported_video);
|
||||||
}
|
}
|
||||||
@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackInfo trackInfo = trackSelector.getTrackInfo();
|
TrackSelections<MappedTrackInfo> trackSelections = trackSelector.getCurrentSelections();
|
||||||
if (trackInfo == null) {
|
if (trackSelections == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rendererCount = trackInfo.rendererCount;
|
int rendererCount = trackSelections.length;
|
||||||
for (int i = 0; i < rendererCount; i++) {
|
for (int i = 0; i < rendererCount; i++) {
|
||||||
TrackGroupArray trackGroups = trackInfo.getTrackGroups(i);
|
TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i);
|
||||||
if (trackGroups.length != 0) {
|
if (trackGroups.length != 0) {
|
||||||
Button button = new Button(this);
|
Button button = new Button(this);
|
||||||
int label;
|
int label;
|
||||||
|
@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup;
|
|||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
|
|
||||||
import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
|
import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
@ -51,7 +51,7 @@ import java.util.Locale;
|
|||||||
private final MappingTrackSelector selector;
|
private final MappingTrackSelector selector;
|
||||||
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
||||||
|
|
||||||
private TrackInfo trackInfo;
|
private MappedTrackInfo trackInfo;
|
||||||
private int rendererIndex;
|
private int rendererIndex;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private boolean[] trackGroupsAdaptive;
|
private boolean[] trackGroupsAdaptive;
|
||||||
@ -82,7 +82,7 @@ import java.util.Locale;
|
|||||||
* @param trackInfo The current track information.
|
* @param trackInfo The current track information.
|
||||||
* @param rendererIndex The index of the renderer.
|
* @param rendererIndex The index of the renderer.
|
||||||
*/
|
*/
|
||||||
public void showSelectionDialog(Activity activity, CharSequence title, TrackInfo trackInfo,
|
public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo,
|
||||||
int rendererIndex) {
|
int rendererIndex) {
|
||||||
this.trackInfo = trackInfo;
|
this.trackInfo = trackInfo;
|
||||||
this.rendererIndex = rendererIndex;
|
this.rendererIndex = rendererIndex;
|
||||||
@ -203,11 +203,7 @@ import java.util.Locale;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
if (isDisabled) {
|
selector.setRendererDisabled(rendererIndex, isDisabled);
|
||||||
selector.setRendererDisabled(rendererIndex, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selector.setRendererDisabled(rendererIndex, false);
|
|
||||||
if (override != null) {
|
if (override != null) {
|
||||||
selector.setSelectionOverride(rendererIndex, trackGroups, override);
|
selector.setSelectionOverride(rendererIndex, trackGroups, override);
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +100,8 @@ public final class CronetDataSourceTest {
|
|||||||
Executor executor, int priority,
|
Executor executor, int priority,
|
||||||
Collection<Object> connectionAnnotations,
|
Collection<Object> connectionAnnotations,
|
||||||
boolean disableCache,
|
boolean disableCache,
|
||||||
boolean disableConnectionMigration);
|
boolean disableConnectionMigration,
|
||||||
|
boolean allowDirectExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@ -108,7 +109,7 @@ public final class CronetDataSourceTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Predicate<String> mockContentTypePredicate;
|
private Predicate<String> mockContentTypePredicate;
|
||||||
@Mock
|
@Mock
|
||||||
private TransferListener mockTransferListener;
|
private TransferListener<CronetDataSource> mockTransferListener;
|
||||||
@Mock
|
@Mock
|
||||||
private Clock mockClock;
|
private Clock mockClock;
|
||||||
@Mock
|
@Mock
|
||||||
@ -143,6 +144,7 @@ public final class CronetDataSourceTest {
|
|||||||
anyInt(),
|
anyInt(),
|
||||||
eq(Collections.emptyList()),
|
eq(Collections.emptyList()),
|
||||||
any(Boolean.class),
|
any(Boolean.class),
|
||||||
|
any(Boolean.class),
|
||||||
any(Boolean.class))).thenReturn(mockUrlRequest);
|
any(Boolean.class))).thenReturn(mockUrlRequest);
|
||||||
mockStatusResponse();
|
mockStatusResponse();
|
||||||
|
|
||||||
@ -170,8 +172,8 @@ public final class CronetDataSourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void testOpeningTwiceThrows() throws HttpDataSourceException, IllegalStateException {
|
public void testOpeningTwiceThrows() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
assertConnectionState(CronetDataSource.IDLE_CONNECTION);
|
assertConnectionState(CronetDataSource.IDLE_CONNECTION);
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
@ -181,7 +183,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
|
public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
dataSourceUnderTest.close();
|
dataSourceUnderTest.close();
|
||||||
@ -194,6 +196,7 @@ public final class CronetDataSourceTest {
|
|||||||
anyInt(),
|
anyInt(),
|
||||||
eq(Collections.emptyList()),
|
eq(Collections.emptyList()),
|
||||||
any(Boolean.class),
|
any(Boolean.class),
|
||||||
|
any(Boolean.class),
|
||||||
any(Boolean.class))).thenReturn(mockUrlRequest2);
|
any(Boolean.class))).thenReturn(mockUrlRequest2);
|
||||||
doAnswer(new Answer<Object>() {
|
doAnswer(new Answer<Object>() {
|
||||||
@Override
|
@Override
|
||||||
@ -214,7 +217,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestStartCalled() throws HttpDataSourceException {
|
public void testRequestStartCalled() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
verify(mockCronetEngine).createRequest(
|
verify(mockCronetEngine).createRequest(
|
||||||
@ -224,13 +227,14 @@ public final class CronetDataSourceTest {
|
|||||||
anyInt(),
|
anyInt(),
|
||||||
eq(Collections.emptyList()),
|
eq(Collections.emptyList()),
|
||||||
any(Boolean.class),
|
any(Boolean.class),
|
||||||
|
any(Boolean.class),
|
||||||
any(Boolean.class));
|
any(Boolean.class));
|
||||||
verify(mockUrlRequest).start();
|
verify(mockUrlRequest).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestHeadersSet() throws HttpDataSourceException {
|
public void testRequestHeadersSet() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||||
testResponseHeader.put("Content-Length", Long.toString(5000L));
|
testResponseHeader.put("Content-Length", Long.toString(5000L));
|
||||||
@ -248,13 +252,29 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestOpen() throws HttpDataSourceException {
|
public void testRequestOpen() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec));
|
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec));
|
||||||
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
|
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
|
||||||
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
|
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRequestOpenGzippedCompressedReturnsDataSpecLength()
|
||||||
|
throws HttpDataSourceException {
|
||||||
|
testResponseHeader.put("Content-Encoding", "gzip");
|
||||||
|
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
|
||||||
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
|
// Data spec's requested length, 5000. Test response's length, 16,000.
|
||||||
|
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||||
|
|
||||||
|
assertEquals(5000 /* contentLength */, dataSourceUnderTest.open(testDataSpec));
|
||||||
|
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
|
||||||
|
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestOpenFail() {
|
public void testRequestOpenFail() {
|
||||||
mockResponseStartFailure();
|
mockResponseStartFailure();
|
||||||
@ -291,7 +311,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestOpenValidatesStatusCode() {
|
public void testRequestOpenValidatesStatusCode() {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
|
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -308,7 +328,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestOpenValidatesContentTypePredicate() {
|
public void testRequestOpenValidatesContentTypePredicate() {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false);
|
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -325,7 +345,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestOpenValidatesContentLength() {
|
public void testRequestOpenValidatesContentLength() {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
// Data spec's requested length, 5000. Test response's length, 16,000.
|
// Data spec's requested length, 5000. Test response's length, 16,000.
|
||||||
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
|
||||||
@ -344,7 +364,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPostRequestOpen() throws HttpDataSourceException {
|
public void testPostRequestOpen() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
|
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
|
||||||
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec));
|
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec));
|
||||||
@ -354,7 +374,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPostRequestOpenValidatesContentType() {
|
public void testPostRequestOpenValidatesContentType() {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dataSourceUnderTest.open(testPostDataSpec);
|
dataSourceUnderTest.open(testPostDataSpec);
|
||||||
@ -366,7 +386,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPostRequestOpenRejects307Redirects() {
|
public void testPostRequestOpenRejects307Redirects() {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockResponseStartRedirect();
|
mockResponseStartRedirect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -380,7 +400,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestReadTwice() throws HttpDataSourceException {
|
public void testRequestReadTwice() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
@ -402,7 +422,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSecondRequestNoContentLength() throws HttpDataSourceException {
|
public void testSecondRequestNoContentLength() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
byte[] returnedBuffer = new byte[8];
|
byte[] returnedBuffer = new byte[8];
|
||||||
@ -433,7 +453,23 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadWithOffset() throws HttpDataSourceException {
|
public void testReadWithOffset() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
mockReadSuccess();
|
||||||
|
|
||||||
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
|
|
||||||
|
byte[] returnedBuffer = new byte[16];
|
||||||
|
int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8);
|
||||||
|
assertArrayEquals(prefixZeros(buildTestDataArray(0, 8), 16), returnedBuffer);
|
||||||
|
assertEquals(8, bytesRead);
|
||||||
|
verify(mockTransferListener).onBytesTransferred(dataSourceUnderTest, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWithUnsetLength() throws HttpDataSourceException {
|
||||||
|
testResponseHeader.remove("Content-Length");
|
||||||
|
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
|
||||||
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
@ -447,7 +483,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadReturnsWhatItCan() throws HttpDataSourceException {
|
public void testReadReturnsWhatItCan() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
@ -461,7 +497,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClosedMeansClosed() throws HttpDataSourceException {
|
public void testClosedMeansClosed() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
@ -489,7 +525,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOverread() throws HttpDataSourceException {
|
public void testOverread() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadSuccess();
|
mockReadSuccess();
|
||||||
|
|
||||||
// Ask for 16 bytes
|
// Ask for 16 bytes
|
||||||
@ -676,7 +712,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExceptionFromTransferListener() throws HttpDataSourceException {
|
public void testExceptionFromTransferListener() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
|
|
||||||
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
|
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
|
||||||
// the subsequent open() call succeeds.
|
// the subsequent open() call succeeds.
|
||||||
@ -695,7 +731,7 @@ public final class CronetDataSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadFailure() throws HttpDataSourceException {
|
public void testReadFailure() throws HttpDataSourceException {
|
||||||
mockResponesStartSuccess();
|
mockResponseStartSuccess();
|
||||||
mockReadFailure();
|
mockReadFailure();
|
||||||
|
|
||||||
dataSourceUnderTest.open(testDataSpec);
|
dataSourceUnderTest.open(testDataSpec);
|
||||||
@ -722,7 +758,7 @@ public final class CronetDataSourceTest {
|
|||||||
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
|
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockResponesStartSuccess() {
|
private void mockResponseStartSuccess() {
|
||||||
doAnswer(new Answer<Object>() {
|
doAnswer(new Answer<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
|||||||
try {
|
try {
|
||||||
validateResponse(info);
|
validateResponse(info);
|
||||||
responseInfo = info;
|
responseInfo = info;
|
||||||
// Check content length.
|
|
||||||
contentLength = getContentLength(info.getAllHeaders());
|
if (isCompressed(info)) {
|
||||||
// If a specific length is requested and a specific length is returned but the 2 don't match
|
contentLength = currentDataSpec.length;
|
||||||
// it's an error.
|
} else {
|
||||||
if (currentDataSpec.length != C.LENGTH_UNSET
|
// Check content length.
|
||||||
&& contentLength != C.LENGTH_UNSET
|
contentLength = getContentLength(info.getAllHeaders());
|
||||||
&& currentDataSpec.length != contentLength) {
|
// If a specific length is requested and a specific length is returned but the 2 don't match
|
||||||
throw new OpenException("Content length did not match requested length", currentDataSpec,
|
// it's an error.
|
||||||
getCurrentRequestStatus());
|
if (currentDataSpec.length != C.LENGTH_UNSET
|
||||||
|
&& contentLength != C.LENGTH_UNSET
|
||||||
|
&& currentDataSpec.length != contentLength) {
|
||||||
|
throw new OpenException("Content length did not match requested length", currentDataSpec,
|
||||||
|
getCurrentRequestStatus());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentLength > 0) {
|
if (contentLength > 0) {
|
||||||
@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} iff the content is compressed.
|
||||||
|
*
|
||||||
|
* <p>If {@code true}, clients cannot use the value of content length from the request headers to
|
||||||
|
* read the data, since Cronet returns the uncompressed data and this content length reflects the
|
||||||
|
* compressed content length.
|
||||||
|
*/
|
||||||
|
private boolean isCompressed(UrlResponseInfo info) {
|
||||||
|
for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) {
|
||||||
|
if (entry.getKey().equalsIgnoreCase("Content-Encoding")) {
|
||||||
|
return !entry.getValue().equalsIgnoreCase("identity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
|
private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
|
||||||
// Check for a valid response code.
|
// Check for a valid response code.
|
||||||
int responseCode = info.getHttpStatusCode();
|
int responseCode = info.getHttpStatusCode();
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
|
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
|
||||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||||
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer();
|
LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer();
|
||||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||||
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0);
|
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0);
|
||||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null);
|
DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
|
||||||
player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector);
|
player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector);
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
ExtractorMediaSource mediaSource = new ExtractorMediaSource(
|
||||||
|
@ -55,6 +55,7 @@ dependencies {
|
|||||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||||
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
||||||
|
compile 'com.android.support:support-annotations:24.2.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
android.libraryVariants.all { variant ->
|
android.libraryVariants.all { variant ->
|
||||||
@ -95,7 +96,7 @@ publish {
|
|||||||
userOrg = 'google'
|
userOrg = 'google'
|
||||||
groupId = 'com.google.android.exoplayer'
|
groupId = 'com.google.android.exoplayer'
|
||||||
artifactId = 'exoplayer'
|
artifactId = 'exoplayer'
|
||||||
version = 'r2.0.0'
|
version = 'r2.0.1'
|
||||||
description = 'The ExoPlayer library.'
|
description = 'The ExoPlayer library.'
|
||||||
website = 'https://github.com/google/ExoPlayer'
|
website = 'https://github.com/google/ExoPlayer'
|
||||||
}
|
}
|
||||||
|
BIN
library/src/androidTest/assets/mp3/play-trimmed.mp3
Normal file
BIN
library/src/androidTest/assets/mp3/play-trimmed.mp3
Normal file
Binary file not shown.
33
library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump
Normal file
33
library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = true
|
||||||
|
duration = 26125
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/mpeg
|
||||||
|
maxInputSize = 4096
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 44100
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 1
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 418, hash B819987
|
||||||
|
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = true
|
||||||
|
duration = 26125
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/mpeg
|
||||||
|
maxInputSize = 4096
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 44100
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 0
|
||||||
|
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = true
|
||||||
|
duration = 26125
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/mpeg
|
||||||
|
maxInputSize = 4096
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 44100
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 0
|
||||||
|
tracksEnded = true
|
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump
Normal file
29
library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = true
|
||||||
|
duration = 26125
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/mpeg
|
||||||
|
maxInputSize = 4096
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 44100
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 0
|
||||||
|
tracksEnded = true
|
@ -0,0 +1,33 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = false
|
||||||
|
duration = UNSET TIME
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/mpeg
|
||||||
|
maxInputSize = 4096
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 44100
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 1
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 418, hash B819987
|
||||||
|
tracksEnded = true
|
@ -8,7 +8,7 @@ track 0:
|
|||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = application/eia-608
|
sampleMimeType = application/cea-608
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
@ -25,305 +25,605 @@ track 0:
|
|||||||
language = null
|
language = null
|
||||||
drmInitData = -
|
drmInitData = -
|
||||||
initializationData:
|
initializationData:
|
||||||
sample count = 75
|
sample count = 150
|
||||||
sample 0:
|
sample 0:
|
||||||
time = 37657512133
|
time = 37657512133
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 1:
|
sample 1:
|
||||||
|
time = 37657528822
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 2:
|
||||||
time = 37657545511
|
time = 37657545511
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6CD
|
data = length 3, hash 766F
|
||||||
sample 2:
|
sample 3:
|
||||||
|
time = 37657562177
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 4:
|
||||||
time = 37657578866
|
time = 37657578866
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6DC
|
data = length 3, hash 767E
|
||||||
sample 3:
|
sample 5:
|
||||||
|
time = 37657595555
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 6:
|
||||||
time = 37657612244
|
time = 37657612244
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF65B
|
data = length 15, hash E4359178
|
||||||
sample 4:
|
sample 7:
|
||||||
|
time = 37657628911
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 8:
|
||||||
time = 37657645600
|
time = 37657645600
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6CD
|
data = length 12, hash 15EBEB66
|
||||||
sample 5:
|
sample 9:
|
||||||
|
time = 37657662288
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 10:
|
||||||
time = 37657678977
|
time = 37657678977
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF67B
|
data = length 3, hash 761D
|
||||||
sample 6:
|
sample 11:
|
||||||
|
time = 37657695644
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 12:
|
||||||
time = 37657712333
|
time = 37657712333
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 2B5
|
data = length 30, hash E181418F
|
||||||
sample 7:
|
sample 13:
|
||||||
|
time = 37657729022
|
||||||
|
flags = 1
|
||||||
|
data = length 6, hash 36289CE2
|
||||||
|
sample 14:
|
||||||
time = 37657745711
|
time = 37657745711
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash F5
|
data = length 12, hash 3C304F5B
|
||||||
sample 8:
|
sample 15:
|
||||||
|
time = 37657762377
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 16:
|
||||||
time = 37657779066
|
time = 37657779066
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF87A
|
data = length 12, hash 88DD8EF6
|
||||||
sample 9:
|
sample 17:
|
||||||
|
time = 37657795755
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 18:
|
||||||
time = 37657812444
|
time = 37657812444
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF698
|
data = length 12, hash 8B411833
|
||||||
sample 10:
|
sample 19:
|
||||||
|
time = 37657829111
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 20:
|
||||||
time = 37657845800
|
time = 37657845800
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1F4
|
data = length 12, hash 742A2DF1
|
||||||
sample 11:
|
sample 21:
|
||||||
|
time = 37657862488
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 22:
|
||||||
time = 37657879177
|
time = 37657879177
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 803
|
data = length 12, hash 9A2ECBEE
|
||||||
sample 12:
|
sample 23:
|
||||||
|
time = 37657895844
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 24:
|
||||||
time = 37657912533
|
time = 37657912533
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1F8
|
data = length 12, hash 562688EA
|
||||||
sample 13:
|
sample 25:
|
||||||
|
time = 37657929222
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 26:
|
||||||
time = 37657945911
|
time = 37657945911
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 117A
|
data = length 12, hash ADE4B953
|
||||||
sample 14:
|
sample 27:
|
||||||
|
time = 37657962577
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 28:
|
||||||
time = 37657979266
|
time = 37657979266
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 166
|
data = length 12, hash F927E3E5
|
||||||
sample 15:
|
sample 29:
|
||||||
|
time = 37657995955
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 30:
|
||||||
time = 37658012644
|
time = 37658012644
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 105A
|
data = length 12, hash EA327945
|
||||||
sample 16:
|
sample 31:
|
||||||
|
time = 37658029311
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 32:
|
||||||
time = 37658046000
|
time = 37658046000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FCF
|
data = length 12, hash 3E5DA13C
|
||||||
sample 17:
|
sample 33:
|
||||||
|
time = 37658062688
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 34:
|
||||||
time = 37658079377
|
time = 37658079377
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1253
|
data = length 12, hash BF646AE3
|
||||||
sample 18:
|
sample 35:
|
||||||
|
time = 37658096044
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 36:
|
||||||
time = 37658112733
|
time = 37658112733
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 11DA
|
data = length 12, hash 41E3BA78
|
||||||
sample 19:
|
sample 37:
|
||||||
|
time = 37658129422
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 38:
|
||||||
time = 37658146111
|
time = 37658146111
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 795
|
data = length 12, hash A2945EF6
|
||||||
sample 20:
|
sample 39:
|
||||||
|
time = 37658162777
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 40:
|
||||||
time = 37658179466
|
time = 37658179466
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 103E
|
data = length 12, hash 26735812
|
||||||
sample 21:
|
sample 41:
|
||||||
|
time = 37658196155
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 42:
|
||||||
time = 37658212844
|
time = 37658212844
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 120F
|
data = length 12, hash DC14D3D8
|
||||||
sample 22:
|
sample 43:
|
||||||
|
time = 37658229511
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 44:
|
||||||
time = 37658246200
|
time = 37658246200
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF698
|
data = length 12, hash 882191BE
|
||||||
sample 23:
|
sample 45:
|
||||||
|
time = 37658262888
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 46:
|
||||||
time = 37658279577
|
time = 37658279577
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1F4
|
data = length 12, hash 8B4886B1
|
||||||
sample 24:
|
sample 47:
|
||||||
|
time = 37658296244
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 48:
|
||||||
time = 37658312933
|
time = 37658312933
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF71B
|
data = length 12, hash 98D98F96
|
||||||
sample 25:
|
sample 49:
|
||||||
|
time = 37658329622
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 50:
|
||||||
time = 37658346311
|
time = 37658346311
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash F91
|
data = length 30, hash CF8E53E3
|
||||||
sample 26:
|
sample 51:
|
||||||
|
time = 37658362977
|
||||||
|
flags = 1
|
||||||
|
data = length 6, hash 36289CE2
|
||||||
|
sample 52:
|
||||||
time = 37658379666
|
time = 37658379666
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 166
|
data = length 12, hash F883C9EE
|
||||||
sample 27:
|
sample 53:
|
||||||
|
time = 37658396355
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 54:
|
||||||
time = 37658413044
|
time = 37658413044
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1023
|
data = length 12, hash 6E6B2B9C
|
||||||
sample 28:
|
sample 55:
|
||||||
|
time = 37658429711
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 56:
|
||||||
time = 37658446400
|
time = 37658446400
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 117A
|
data = length 12, hash B4FE7F08
|
||||||
sample 29:
|
sample 57:
|
||||||
|
time = 37658463088
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 58:
|
||||||
time = 37658479777
|
time = 37658479777
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 784
|
data = length 12, hash 5A1EA7C7
|
||||||
sample 30:
|
sample 59:
|
||||||
|
time = 37658496444
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 60:
|
||||||
time = 37658513133
|
time = 37658513133
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1F8
|
data = length 12, hash 46BD6CC9
|
||||||
sample 31:
|
sample 61:
|
||||||
|
time = 37658529822
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 62:
|
||||||
time = 37658546511
|
time = 37658546511
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 10D9
|
data = length 12, hash 1B1E2554
|
||||||
sample 32:
|
sample 63:
|
||||||
|
time = 37658563177
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 64:
|
||||||
time = 37658579866
|
time = 37658579866
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 935
|
data = length 12, hash 91FCC537
|
||||||
sample 33:
|
sample 65:
|
||||||
|
time = 37658596555
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 66:
|
||||||
time = 37658613244
|
time = 37658613244
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 2B5
|
data = length 12, hash A9355E1B
|
||||||
sample 34:
|
sample 67:
|
||||||
|
time = 37658629911
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 68:
|
||||||
time = 37658646600
|
time = 37658646600
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash F5
|
data = length 12, hash 2511F69B
|
||||||
sample 35:
|
sample 69:
|
||||||
|
time = 37658663288
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 70:
|
||||||
time = 37658679977
|
time = 37658679977
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF87A
|
data = length 12, hash 90925736
|
||||||
sample 36:
|
sample 71:
|
||||||
|
time = 37658696644
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 72:
|
||||||
time = 37658713333
|
time = 37658713333
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF698
|
data = length 21, hash 431EEE30
|
||||||
sample 37:
|
sample 73:
|
||||||
|
time = 37658730022
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 74:
|
||||||
time = 37658746711
|
time = 37658746711
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1F4
|
data = length 12, hash 7BDEF631
|
||||||
sample 38:
|
sample 75:
|
||||||
|
time = 37658763377
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 76:
|
||||||
time = 37658780066
|
time = 37658780066
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 793
|
data = length 12, hash A2EEF59E
|
||||||
sample 39:
|
sample 77:
|
||||||
|
time = 37658796755
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 78:
|
||||||
time = 37658813444
|
time = 37658813444
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FF0
|
data = length 12, hash BFC6C022
|
||||||
sample 40:
|
sample 79:
|
||||||
|
time = 37658830111
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 80:
|
||||||
time = 37658846800
|
time = 37658846800
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 16B
|
data = length 12, hash CD4D8FCA
|
||||||
sample 41:
|
sample 81:
|
||||||
|
time = 37658863488
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 82:
|
||||||
time = 37658880177
|
time = 37658880177
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 2C0
|
data = length 12, hash 2BDE8EFA
|
||||||
sample 42:
|
sample 83:
|
||||||
|
time = 37658896844
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 84:
|
||||||
time = 37658913533
|
time = 37658913533
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF953
|
data = length 12, hash 8C858812
|
||||||
sample 43:
|
sample 85:
|
||||||
|
time = 37658930222
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 86:
|
||||||
time = 37658946911
|
time = 37658946911
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 12, hash DE7D0E31
|
||||||
sample 44:
|
sample 87:
|
||||||
|
time = 37658963577
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 88:
|
||||||
time = 37658980266
|
time = 37658980266
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 45:
|
sample 89:
|
||||||
|
time = 37658996955
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 90:
|
||||||
time = 37659013644
|
time = 37659013644
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 46:
|
sample 91:
|
||||||
|
time = 37659030311
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 92:
|
||||||
time = 37659047000
|
time = 37659047000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 47:
|
sample 93:
|
||||||
|
time = 37659063688
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 94:
|
||||||
time = 37659080377
|
time = 37659080377
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 48:
|
sample 95:
|
||||||
|
time = 37659097044
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 96:
|
||||||
time = 37659113733
|
time = 37659113733
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 49:
|
sample 97:
|
||||||
|
time = 37659130422
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 98:
|
||||||
time = 37659147111
|
time = 37659147111
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 50:
|
sample 99:
|
||||||
|
time = 37659163777
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 100:
|
||||||
time = 37659180466
|
time = 37659180466
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 51:
|
sample 101:
|
||||||
|
time = 37659197155
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 102:
|
||||||
time = 37659213844
|
time = 37659213844
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 52:
|
sample 103:
|
||||||
|
time = 37659230511
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 104:
|
||||||
time = 37659247200
|
time = 37659247200
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 53:
|
sample 105:
|
||||||
|
time = 37659263888
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 106:
|
||||||
time = 37659280577
|
time = 37659280577
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 54:
|
sample 107:
|
||||||
|
time = 37659297244
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 108:
|
||||||
time = 37659313933
|
time = 37659313933
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 55:
|
sample 109:
|
||||||
|
time = 37659330622
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 110:
|
||||||
time = 37659347311
|
time = 37659347311
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 56:
|
sample 111:
|
||||||
|
time = 37659363977
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 112:
|
||||||
time = 37659380666
|
time = 37659380666
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 57:
|
sample 113:
|
||||||
|
time = 37659397355
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 114:
|
||||||
time = 37659414044
|
time = 37659414044
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 58:
|
sample 115:
|
||||||
|
time = 37659430711
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 116:
|
||||||
time = 37659447400
|
time = 37659447400
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 59:
|
sample 117:
|
||||||
|
time = 37659464088
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 118:
|
||||||
time = 37659480777
|
time = 37659480777
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 60:
|
sample 119:
|
||||||
|
time = 37659497444
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 120:
|
||||||
time = 37659514133
|
time = 37659514133
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 61:
|
sample 121:
|
||||||
|
time = 37659530822
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 122:
|
||||||
time = 37659547511
|
time = 37659547511
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 62:
|
sample 123:
|
||||||
|
time = 37659564177
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 124:
|
||||||
time = 37659580866
|
time = 37659580866
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF3C1
|
data = length 3, hash 7363
|
||||||
sample 63:
|
sample 125:
|
||||||
|
time = 37659597555
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 126:
|
||||||
time = 37659614244
|
time = 37659614244
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6CD
|
data = length 3, hash 766F
|
||||||
sample 64:
|
sample 127:
|
||||||
|
time = 37659630911
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 128:
|
||||||
time = 37659647600
|
time = 37659647600
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6DC
|
data = length 3, hash 767E
|
||||||
sample 65:
|
sample 129:
|
||||||
|
time = 37659664288
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 130:
|
||||||
time = 37659680977
|
time = 37659680977
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF65B
|
data = length 15, hash 191B585A
|
||||||
sample 66:
|
sample 131:
|
||||||
|
time = 37659697644
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 132:
|
||||||
time = 37659714333
|
time = 37659714333
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6CD
|
data = length 12, hash 15EC5FC5
|
||||||
sample 67:
|
sample 133:
|
||||||
|
time = 37659731022
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 134:
|
||||||
time = 37659747711
|
time = 37659747711
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6FF
|
data = length 3, hash 76A1
|
||||||
sample 68:
|
sample 135:
|
||||||
|
time = 37659764377
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 136:
|
||||||
time = 37659781066
|
time = 37659781066
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF6AC
|
data = length 30, hash E8012479
|
||||||
sample 69:
|
sample 137:
|
||||||
|
time = 37659797755
|
||||||
|
flags = 1
|
||||||
|
data = length 6, hash 36289D5E
|
||||||
|
sample 138:
|
||||||
time = 37659814444
|
time = 37659814444
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFF5FE
|
data = length 12, hash D32F29F3
|
||||||
sample 70:
|
sample 139:
|
||||||
|
time = 37659831111
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 140:
|
||||||
time = 37659847800
|
time = 37659847800
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash FFFFFEF7
|
data = length 21, hash 6258623
|
||||||
sample 71:
|
sample 141:
|
||||||
|
time = 37659864488
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 142:
|
||||||
time = 37659881177
|
time = 37659881177
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 120C
|
data = length 12, hash FE69ABA2
|
||||||
sample 72:
|
sample 143:
|
||||||
|
time = 37659897844
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 144:
|
||||||
time = 37659914533
|
time = 37659914533
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1124
|
data = length 12, hash 958D0815
|
||||||
sample 73:
|
sample 145:
|
||||||
|
time = 37659931222
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 146:
|
||||||
time = 37659947911
|
time = 37659947911
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 1A9
|
data = length 12, hash FF57BFD8
|
||||||
sample 74:
|
sample 147:
|
||||||
|
time = 37659964577
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
|
sample 148:
|
||||||
time = 37659981266
|
time = 37659981266
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 2, hash 935
|
data = length 12, hash 922122E7
|
||||||
|
sample 149:
|
||||||
|
time = 37659997955
|
||||||
|
flags = 1
|
||||||
|
data = length 3, hash 7724
|
||||||
tracksEnded = true
|
tracksEnded = true
|
||||||
|
@ -33,14 +33,14 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};
|
private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
private static final int LARGE_TEST_DATA_LENGTH = 8192;
|
private static final int LARGE_TEST_DATA_LENGTH = 8192;
|
||||||
|
|
||||||
public void testInitialPosition() throws IOException {
|
public void testInitialPosition() throws Exception {
|
||||||
FakeDataSource testDataSource = buildDataSource();
|
FakeDataSource testDataSource = buildDataSource();
|
||||||
DefaultExtractorInput input =
|
DefaultExtractorInput input =
|
||||||
new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET);
|
new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET);
|
||||||
assertEquals(123, input.getPosition());
|
assertEquals(123, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRead() throws IOException, InterruptedException {
|
public void testRead() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
// We expect to perform three reads of three bytes, as setup in buildTestDataSource.
|
// We expect to perform three reads of three bytes, as setup in buildTestDataSource.
|
||||||
@ -58,7 +58,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadPeeked() throws IOException, InterruptedException {
|
public void testReadPeeked() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertTrue(Arrays.equals(TEST_DATA, target));
|
assertTrue(Arrays.equals(TEST_DATA, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadMoreDataPeeked() throws IOException, InterruptedException {
|
public void testReadMoreDataPeeked() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertTrue(Arrays.equals(TEST_DATA, target));
|
assertTrue(Arrays.equals(TEST_DATA, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFullyOnce() throws IOException, InterruptedException {
|
public void testReadFullyOnce() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
input.readFully(target, 0, TEST_DATA.length);
|
input.readFully(target, 0, TEST_DATA.length);
|
||||||
@ -103,7 +103,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFullyTwice() throws IOException, InterruptedException {
|
public void testReadFullyTwice() throws Exception {
|
||||||
// Read TEST_DATA in two parts.
|
// Read TEST_DATA in two parts.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[5];
|
byte[] target = new byte[5];
|
||||||
@ -116,7 +116,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(5 + 4, input.getPosition());
|
assertEquals(5 + 4, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFullyTooMuch() throws IOException, InterruptedException {
|
public void testReadFullyTooMuch() throws Exception {
|
||||||
// Read more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
// Read more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
try {
|
try {
|
||||||
@ -141,7 +141,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(0, input.getPosition());
|
assertEquals(0, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFullyWithFailingDataSource() throws IOException, InterruptedException {
|
public void testReadFullyWithFailingDataSource() throws Exception {
|
||||||
FakeDataSource testDataSource = buildFailingDataSource();
|
FakeDataSource testDataSource = buildFailingDataSource();
|
||||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||||
try {
|
try {
|
||||||
@ -155,7 +155,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(0, input.getPosition());
|
assertEquals(0, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFullyHalfPeeked() throws IOException, InterruptedException {
|
public void testReadFullyHalfPeeked() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(TEST_DATA.length, input.getPosition());
|
assertEquals(TEST_DATA.length, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkip() throws IOException, InterruptedException {
|
public void testSkip() throws Exception {
|
||||||
FakeDataSource testDataSource = buildDataSource();
|
FakeDataSource testDataSource = buildDataSource();
|
||||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||||
// We expect to perform three skips of three bytes, as setup in buildTestDataSource.
|
// We expect to perform three skips of three bytes, as setup in buildTestDataSource.
|
||||||
@ -180,7 +180,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLargeSkip() throws IOException, InterruptedException {
|
public void testLargeSkip() throws Exception {
|
||||||
FakeDataSource testDataSource = buildLargeDataSource();
|
FakeDataSource testDataSource = buildLargeDataSource();
|
||||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||||
// Check that skipping the entire data source succeeds.
|
// Check that skipping the entire data source succeeds.
|
||||||
@ -190,7 +190,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyOnce() throws IOException, InterruptedException {
|
public void testSkipFullyOnce() throws Exception {
|
||||||
// Skip TEST_DATA.
|
// Skip TEST_DATA.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
input.skipFully(TEST_DATA.length);
|
input.skipFully(TEST_DATA.length);
|
||||||
@ -207,7 +207,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyTwice() throws IOException, InterruptedException {
|
public void testSkipFullyTwice() throws Exception {
|
||||||
// Skip TEST_DATA in two parts.
|
// Skip TEST_DATA in two parts.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
input.skipFully(5);
|
input.skipFully(5);
|
||||||
@ -216,7 +216,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(5 + 4, input.getPosition());
|
assertEquals(5 + 4, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyTwicePeeked() throws IOException, InterruptedException {
|
public void testSkipFullyTwicePeeked() throws Exception {
|
||||||
// Skip TEST_DATA.
|
// Skip TEST_DATA.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(TEST_DATA.length, input.getPosition());
|
assertEquals(TEST_DATA.length, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyTooMuch() throws IOException, InterruptedException {
|
public void testSkipFullyTooMuch() throws Exception {
|
||||||
// Skip more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
// Skip more than TEST_DATA. Should fail with an EOFException. Position should not update.
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
try {
|
try {
|
||||||
@ -253,7 +253,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(0, input.getPosition());
|
assertEquals(0, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyWithFailingDataSource() throws IOException, InterruptedException {
|
public void testSkipFullyWithFailingDataSource() throws Exception {
|
||||||
FakeDataSource testDataSource = buildFailingDataSource();
|
FakeDataSource testDataSource = buildFailingDataSource();
|
||||||
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||||
try {
|
try {
|
||||||
@ -266,7 +266,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertEquals(0, input.getPosition());
|
assertEquals(0, input.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipFullyLarge() throws IOException, InterruptedException {
|
public void testSkipFullyLarge() throws Exception {
|
||||||
// Tests skipping an amount of data that's larger than any internal scratch space.
|
// Tests skipping an amount of data that's larger than any internal scratch space.
|
||||||
int largeSkipSize = 1024 * 1024;
|
int largeSkipSize = 1024 * 1024;
|
||||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||||
@ -286,7 +286,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPeekFully() throws IOException, InterruptedException {
|
public void testPeekFully() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
input.peekFully(target, 0, TEST_DATA.length);
|
input.peekFully(target, 0, TEST_DATA.length);
|
||||||
@ -312,7 +312,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testResetPeekPosition() throws IOException, InterruptedException {
|
public void testResetPeekPosition() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
input.peekFully(target, 0, TEST_DATA.length);
|
input.peekFully(target, 0, TEST_DATA.length);
|
||||||
@ -336,8 +336,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds()
|
public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception {
|
||||||
throws IOException, InterruptedException {
|
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -348,8 +347,24 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
assertFalse(input.peekFully(target, 0, 1, true));
|
assertFalse(input.peekFully(target, 0, 1, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails()
|
public void testPeekFullyAtEndThenReadEndOfInput() throws Exception {
|
||||||
throws IOException, InterruptedException {
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
|
// Peek up to the end of the input.
|
||||||
|
assertTrue(input.peekFully(target, 0, TEST_DATA.length, false));
|
||||||
|
|
||||||
|
// Peek the end of the input.
|
||||||
|
assertFalse(input.peekFully(target, 0, 1, true));
|
||||||
|
|
||||||
|
// Read up to the end of the input.
|
||||||
|
assertTrue(input.readFully(target, 0, TEST_DATA.length, false));
|
||||||
|
|
||||||
|
// Read the end of the input.
|
||||||
|
assertFalse(input.readFully(target, 0, 1, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() throws Exception {
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -365,8 +380,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails()
|
public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception {
|
||||||
throws IOException, InterruptedException {
|
|
||||||
DefaultExtractorInput input = createDefaultExtractorInput();
|
DefaultExtractorInput input = createDefaultExtractorInput();
|
||||||
byte[] target = new byte[TEST_DATA.length];
|
byte[] target = new byte[TEST_DATA.length];
|
||||||
|
|
||||||
@ -382,7 +396,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FakeDataSource buildDataSource() throws IOException {
|
private static FakeDataSource buildDataSource() throws Exception {
|
||||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3));
|
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3));
|
||||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6));
|
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6));
|
||||||
@ -392,7 +406,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
return testDataSource;
|
return testDataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FakeDataSource buildFailingDataSource() throws IOException {
|
private static FakeDataSource buildFailingDataSource() throws Exception {
|
||||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||||
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6));
|
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6));
|
||||||
builder.appendReadError(new IOException());
|
builder.appendReadError(new IOException());
|
||||||
@ -402,7 +416,7 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
return testDataSource;
|
return testDataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FakeDataSource buildLargeDataSource() throws IOException {
|
private static FakeDataSource buildLargeDataSource() throws Exception {
|
||||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||||
builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
|
builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
|
||||||
FakeDataSource testDataSource = builder.build();
|
FakeDataSource testDataSource = builder.build();
|
||||||
@ -410,8 +424,9 @@ public class DefaultExtractorInputTest extends TestCase {
|
|||||||
return testDataSource;
|
return testDataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DefaultExtractorInput createDefaultExtractorInput() throws IOException {
|
private static DefaultExtractorInput createDefaultExtractorInput() throws Exception {
|
||||||
FakeDataSource testDataSource = buildDataSource();
|
FakeDataSource testDataSource = buildDataSource();
|
||||||
return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
|
|||||||
}, "mp3/bear.mp3", getInstrumentation());
|
}, "mp3/bear.mp3", getInstrumentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testTrimmedMp3Sample() throws Exception {
|
||||||
|
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
|
||||||
|
@Override
|
||||||
|
public Extractor create() {
|
||||||
|
return new Mp3Extractor();
|
||||||
|
}
|
||||||
|
}, "mp3/play-trimmed.mp3", getInstrumentation());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,16 @@
|
|||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
}, "ts/sample.ts", fileData, getInstrumentation());
|
}, "ts/sample.ts", fileData, getInstrumentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCustomPesReader() throws Exception {
|
||||||
|
CustomEsReaderFactory factory = new CustomEsReaderFactory();
|
||||||
|
TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory);
|
||||||
|
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||||
|
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
|
||||||
|
.setSimulateIOErrors(false)
|
||||||
|
.setSimulateUnknownLength(false)
|
||||||
|
.setSimulatePartialReads(false).build();
|
||||||
|
FakeExtractorOutput output = new FakeExtractorOutput();
|
||||||
|
tsExtractor.init(output);
|
||||||
|
tsExtractor.seek(input.getPosition());
|
||||||
|
PositionHolder seekPositionHolder = new PositionHolder();
|
||||||
|
int readResult = Extractor.RESULT_CONTINUE;
|
||||||
|
while (readResult != Extractor.RESULT_END_OF_INPUT) {
|
||||||
|
readResult = tsExtractor.read(input, seekPositionHolder);
|
||||||
|
}
|
||||||
|
CustomEsReader reader = factory.reader;
|
||||||
|
assertEquals(2, reader.packetsRead);
|
||||||
|
TrackOutput trackOutput = reader.getTrackOutput();
|
||||||
|
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
|
||||||
|
assertEquals(
|
||||||
|
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
|
||||||
|
((FakeTrackOutput) trackOutput).format);
|
||||||
|
}
|
||||||
|
|
||||||
private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
|
private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (((byte) i) == TS_SYNC_BYTE) {
|
if (((byte) i) == TS_SYNC_BYTE) {
|
||||||
@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class CustomEsReader extends ElementaryStreamReader {
|
||||||
|
|
||||||
|
public int packetsRead = 0;
|
||||||
|
|
||||||
|
public CustomEsReader(TrackOutput output, String language) {
|
||||||
|
super(output);
|
||||||
|
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
|
||||||
|
language, null, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetFinished() {
|
||||||
|
packetsRead++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackOutput getTrackOutput() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory {
|
||||||
|
|
||||||
|
private final ElementaryStreamReader.Factory defaultFactory;
|
||||||
|
private CustomEsReader reader;
|
||||||
|
|
||||||
|
public CustomEsReaderFactory() {
|
||||||
|
defaultFactory = new DefaultStreamReaderFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
|
||||||
|
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
|
||||||
|
if (streamType == 3) {
|
||||||
|
// We need to manually avoid a duplicate custom reader creation.
|
||||||
|
if (reader == null) {
|
||||||
|
reader = new CustomEsReader(output.track(pid), esInfo.language);
|
||||||
|
}
|
||||||
|
return reader;
|
||||||
|
} else {
|
||||||
|
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,11 @@ package com.google.android.exoplayer2;
|
|||||||
|
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,55 +73,79 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final String UTF8_NAME = "UTF-8";
|
public static final String UTF8_NAME = "UTF-8";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crypto modes for a codec.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
||||||
|
public @interface CryptoMode {}
|
||||||
|
/**
|
||||||
|
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("InlinedApi")
|
||||||
|
public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
|
||||||
/**
|
/**
|
||||||
* @see MediaCodec#CRYPTO_MODE_AES_CTR
|
* @see MediaCodec#CRYPTO_MODE_AES_CTR
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
||||||
|
/**
|
||||||
|
* @see MediaCodec#CRYPTO_MODE_AES_CBC
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("InlinedApi")
|
||||||
|
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an audio encoding, or an invalid or unset value.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
||||||
|
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS,
|
||||||
|
ENCODING_DTS_HD})
|
||||||
|
public @interface Encoding {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a PCM audio encoding, or an invalid or unset value.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
||||||
|
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT})
|
||||||
|
public @interface PcmEncoding {}
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_INVALID
|
* @see AudioFormat#ENCODING_INVALID
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_PCM_8BIT
|
* @see AudioFormat#ENCODING_PCM_8BIT
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_PCM_16BIT
|
* @see AudioFormat#ENCODING_PCM_16BIT
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PCM encoding with 24 bits per sample.
|
* PCM encoding with 24 bits per sample.
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_PCM_24BIT = 0x80000000;
|
public static final int ENCODING_PCM_24BIT = 0x80000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PCM encoding with 32 bits per sample.
|
* PCM encoding with 32 bits per sample.
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_PCM_32BIT = 0x40000000;
|
public static final int ENCODING_PCM_32BIT = 0x40000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_AC3
|
* @see AudioFormat#ENCODING_AC3
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_E_AC3
|
* @see AudioFormat#ENCODING_E_AC3
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_DTS
|
* @see AudioFormat#ENCODING_DTS
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AudioFormat#ENCODING_DTS_HD
|
* @see AudioFormat#ENCODING_DTS_HD
|
||||||
*/
|
*/
|
||||||
@ -132,48 +159,93 @@ public final class C {
|
|||||||
public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
|
public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
|
||||||
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags which can apply to a buffer containing a media sample.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY})
|
||||||
|
public @interface BufferFlags {}
|
||||||
/**
|
/**
|
||||||
* Indicates that a buffer holds a synchronization sample.
|
* Indicates that a buffer holds a synchronization sample.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
|
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag for empty buffers that signal that the end of the stream was reached.
|
* Flag for empty buffers that signal that the end of the stream was reached.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that a buffer is (at least partially) encrypted.
|
* Indicates that a buffer is (at least partially) encrypted.
|
||||||
*/
|
*/
|
||||||
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
|
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that a buffer should be decoded but not rendered.
|
* Indicates that a buffer should be decoded but not rendered.
|
||||||
*/
|
*/
|
||||||
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000;
|
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track selection flags.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED,
|
||||||
|
SELECTION_FLAG_AUTOSELECT})
|
||||||
|
public @interface SelectionFlags {}
|
||||||
|
/**
|
||||||
|
* Indicates that the track should be selected if user preferences do not state otherwise.
|
||||||
|
*/
|
||||||
|
public static final int SELECTION_FLAG_DEFAULT = 1;
|
||||||
|
/**
|
||||||
|
* Indicates that the track must be displayed. Only applies to text tracks.
|
||||||
|
*/
|
||||||
|
public static final int SELECTION_FLAG_FORCED = 2;
|
||||||
|
/**
|
||||||
|
* Indicates that the player may choose to play the track in absence of an explicit user
|
||||||
|
* preference.
|
||||||
|
*/
|
||||||
|
public static final int SELECTION_FLAG_AUTOSELECT = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a streaming or other media type.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
|
||||||
|
public @interface ContentType {}
|
||||||
|
/**
|
||||||
|
* Value returned by {@link Util#inferContentType(String)} for DASH manifests.
|
||||||
|
*/
|
||||||
|
public static final int TYPE_DASH = 0;
|
||||||
|
/**
|
||||||
|
* Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests.
|
||||||
|
*/
|
||||||
|
public static final int TYPE_SS = 1;
|
||||||
|
/**
|
||||||
|
* Value returned by {@link Util#inferContentType(String)} for HLS manifests.
|
||||||
|
*/
|
||||||
|
public static final int TYPE_HLS = 2;
|
||||||
|
/**
|
||||||
|
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
|
||||||
|
* Smooth Streaming manifests.
|
||||||
|
*/
|
||||||
|
public static final int TYPE_OTHER = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A return value for methods where the end of an input was encountered.
|
* A return value for methods where the end of an input was encountered.
|
||||||
*/
|
*/
|
||||||
public static final int RESULT_END_OF_INPUT = -1;
|
public static final int RESULT_END_OF_INPUT = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
|
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
|
||||||
*/
|
*/
|
||||||
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
|
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A return value for methods where nothing was read.
|
* A return value for methods where nothing was read.
|
||||||
*/
|
*/
|
||||||
public static final int RESULT_NOTHING_READ = -3;
|
public static final int RESULT_NOTHING_READ = -3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A return value for methods where a buffer was read.
|
* A return value for methods where a buffer was read.
|
||||||
*/
|
*/
|
||||||
public static final int RESULT_BUFFER_READ = -4;
|
public static final int RESULT_BUFFER_READ = -4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A return value for methods where a format was read.
|
* A return value for methods where a format was read.
|
||||||
*/
|
*/
|
||||||
@ -183,32 +255,26 @@ public final class C {
|
|||||||
* A data type constant for data of unknown or unspecified type.
|
* A data type constant for data of unknown or unspecified type.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_UNKNOWN = 0;
|
public static final int DATA_TYPE_UNKNOWN = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data type constant for media, typically containing media samples.
|
* A data type constant for media, typically containing media samples.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_MEDIA = 1;
|
public static final int DATA_TYPE_MEDIA = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data type constant for media, typically containing only initialization data.
|
* A data type constant for media, typically containing only initialization data.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
|
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data type constant for drm or encryption data.
|
* A data type constant for drm or encryption data.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_DRM = 3;
|
public static final int DATA_TYPE_DRM = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data type constant for a manifest file.
|
* A data type constant for a manifest file.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_MANIFEST = 4;
|
public static final int DATA_TYPE_MANIFEST = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data type constant for time synchronization data.
|
* A data type constant for time synchronization data.
|
||||||
*/
|
*/
|
||||||
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
|
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
|
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
|
||||||
* equal to this value.
|
* equal to this value.
|
||||||
@ -219,32 +285,26 @@ public final class C {
|
|||||||
* A type constant for tracks of unknown type.
|
* A type constant for tracks of unknown type.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_UNKNOWN = -1;
|
public static final int TRACK_TYPE_UNKNOWN = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type constant for tracks of some default type, where the type itself is unknown.
|
* A type constant for tracks of some default type, where the type itself is unknown.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_DEFAULT = 0;
|
public static final int TRACK_TYPE_DEFAULT = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type constant for audio tracks.
|
* A type constant for audio tracks.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_AUDIO = 1;
|
public static final int TRACK_TYPE_AUDIO = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type constant for video tracks.
|
* A type constant for video tracks.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_VIDEO = 2;
|
public static final int TRACK_TYPE_VIDEO = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type constant for text tracks.
|
* A type constant for text tracks.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_TEXT = 3;
|
public static final int TRACK_TYPE_TEXT = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type constant for metadata tracks.
|
* A type constant for metadata tracks.
|
||||||
*/
|
*/
|
||||||
public static final int TRACK_TYPE_METADATA = 4;
|
public static final int TRACK_TYPE_METADATA = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
|
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
|
||||||
* equal to this value.
|
* equal to this value.
|
||||||
@ -255,27 +315,22 @@ public final class C {
|
|||||||
* A selection reason constant for selections whose reasons are unknown or unspecified.
|
* A selection reason constant for selections whose reasons are unknown or unspecified.
|
||||||
*/
|
*/
|
||||||
public static final int SELECTION_REASON_UNKNOWN = 0;
|
public static final int SELECTION_REASON_UNKNOWN = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A selection reason constant for an initial track selection.
|
* A selection reason constant for an initial track selection.
|
||||||
*/
|
*/
|
||||||
public static final int SELECTION_REASON_INITIAL = 1;
|
public static final int SELECTION_REASON_INITIAL = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A selection reason constant for an manual (i.e. user initiated) track selection.
|
* A selection reason constant for an manual (i.e. user initiated) track selection.
|
||||||
*/
|
*/
|
||||||
public static final int SELECTION_REASON_MANUAL = 2;
|
public static final int SELECTION_REASON_MANUAL = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A selection reason constant for an adaptive track selection.
|
* A selection reason constant for an adaptive track selection.
|
||||||
*/
|
*/
|
||||||
public static final int SELECTION_REASON_ADAPTIVE = 3;
|
public static final int SELECTION_REASON_ADAPTIVE = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A selection reason constant for a trick play track selection.
|
* A selection reason constant for a trick play track selection.
|
||||||
*/
|
*/
|
||||||
public static final int SELECTION_REASON_TRICK_PLAY = 4;
|
public static final int SELECTION_REASON_TRICK_PLAY = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
|
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
|
||||||
* or equal to this value.
|
* or equal to this value.
|
||||||
@ -363,16 +418,20 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final int MSG_CUSTOM_BASE = 10000;
|
public static final int MSG_CUSTOM_BASE = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stereo mode for 360/3D/VR videos.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
|
||||||
|
public @interface StereoMode {}
|
||||||
/**
|
/**
|
||||||
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
|
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
|
||||||
*/
|
*/
|
||||||
public static final int STEREO_MODE_MONO = 0;
|
public static final int STEREO_MODE_MONO = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
|
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
|
||||||
*/
|
*/
|
||||||
public static final int STEREO_MODE_TOP_BOTTOM = 1;
|
public static final int STEREO_MODE_TOP_BOTTOM = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
|
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||||
TrackSelectionArray trackSelections) {
|
TrackSelections<?> trackSelections) {
|
||||||
targetBufferSize = 0;
|
targetBufferSize = 0;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
if (trackSelections.get(i) != null) {
|
if (trackSelections.get(i) != null) {
|
||||||
|
@ -15,15 +15,24 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a non-recoverable playback failure occurs.
|
* Thrown when a non-recoverable playback failure occurs.
|
||||||
*/
|
*/
|
||||||
public final class ExoPlaybackException extends Exception {
|
public final class ExoPlaybackException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of source that produced the error.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})
|
||||||
|
public @interface Type {}
|
||||||
/**
|
/**
|
||||||
* The error occurred loading data from a {@link MediaSource}.
|
* The error occurred loading data from a {@link MediaSource}.
|
||||||
* <p>
|
* <p>
|
||||||
@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception {
|
|||||||
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
|
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
|
||||||
* {@link #TYPE_UNEXPECTED}.
|
* {@link #TYPE_UNEXPECTED}.
|
||||||
*/
|
*/
|
||||||
|
@Type
|
||||||
public final int type;
|
public final int type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception {
|
|||||||
return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET);
|
return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException(int type, String message, Throwable cause, int rendererIndex) {
|
private ExoPlaybackException(@Type int type, String message, Throwable cause,
|
||||||
|
int rendererIndex) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.rendererIndex = rendererIndex;
|
this.rendererIndex = rendererIndex;
|
||||||
|
@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
if (timeline == null || pendingSeekAcks > 0) {
|
if (timeline == null || pendingSeekAcks > 0) {
|
||||||
return maskingWindowPositionMs;
|
return maskingWindowPositionMs;
|
||||||
} else {
|
} else {
|
||||||
int periodIndex = playbackInfo.periodIndex;
|
timeline.getPeriod(playbackInfo.periodIndex, period);
|
||||||
timeline.getPeriod(periodIndex, period);
|
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
|
||||||
int windowIndex = period.windowIndex;
|
|
||||||
timeline.getWindow(windowIndex, window);
|
|
||||||
if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
|
|
||||||
&& window.getPositionInFirstPeriodUs() == 0
|
|
||||||
&& window.getDurationUs() == period.getDurationUs()) {
|
|
||||||
return C.usToMs(playbackInfo.bufferedPositionUs);
|
|
||||||
}
|
|
||||||
return getCurrentPosition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
|||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MediaClock;
|
import com.google.android.exoplayer2.util.MediaClock;
|
||||||
@ -40,8 +40,8 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
* Implements the internal behavior of {@link ExoPlayerImpl}.
|
||||||
*/
|
*/
|
||||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback,
|
/* package */ final class ExoPlayerImplInternal<T> implements Handler.Callback,
|
||||||
TrackSelector.InvalidationListener, MediaSource.Listener {
|
MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playback position information which is read on the application's thread by
|
* Playback position information which is read on the application's thread by
|
||||||
@ -100,7 +100,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
private final Renderer[] renderers;
|
private final Renderer[] renderers;
|
||||||
private final RendererCapabilities[] rendererCapabilities;
|
private final RendererCapabilities[] rendererCapabilities;
|
||||||
private final TrackSelector trackSelector;
|
private final TrackSelector<T> trackSelector;
|
||||||
private final LoadControl loadControl;
|
private final LoadControl loadControl;
|
||||||
private final StandaloneMediaClock standaloneMediaClock;
|
private final StandaloneMediaClock standaloneMediaClock;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
@ -128,13 +128,13 @@ import java.io.IOException;
|
|||||||
private boolean isTimelineReady;
|
private boolean isTimelineReady;
|
||||||
private boolean isTimelineEnded;
|
private boolean isTimelineEnded;
|
||||||
private int bufferAheadPeriodCount;
|
private int bufferAheadPeriodCount;
|
||||||
private MediaPeriodHolder playingPeriodHolder;
|
private MediaPeriodHolder<T> playingPeriodHolder;
|
||||||
private MediaPeriodHolder readingPeriodHolder;
|
private MediaPeriodHolder<T> readingPeriodHolder;
|
||||||
private MediaPeriodHolder loadingPeriodHolder;
|
private MediaPeriodHolder<T> loadingPeriodHolder;
|
||||||
|
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
|
|
||||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
|
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector<T> trackSelector,
|
||||||
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
|
||||||
PlaybackInfo playbackInfo) {
|
PlaybackInfo playbackInfo) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
@ -458,12 +458,11 @@ import java.io.IOException;
|
|||||||
startRenderers();
|
startRenderers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (state == ExoPlayer.STATE_READY) {
|
} else if (state == ExoPlayer.STATE_READY
|
||||||
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady) {
|
&& (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady)) {
|
||||||
rebuffering = playWhenReady;
|
rebuffering = playWhenReady;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == ExoPlayer.STATE_BUFFERING) {
|
if (state == ExoPlayer.STATE_BUFFERING) {
|
||||||
@ -530,17 +529,17 @@ import java.io.IOException;
|
|||||||
rebuffering = false;
|
rebuffering = false;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
|
|
||||||
if (periodPositionUs == C.TIME_UNSET
|
if (periodPositionUs == C.TIME_UNSET || (readingPeriodHolder != playingPeriodHolder
|
||||||
|| (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index
|
&& (periodIndex == playingPeriodHolder.index
|
||||||
|| (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) {
|
|| periodIndex == readingPeriodHolder.index))) {
|
||||||
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
// Clear the timeline because either the seek position is not known, or a renderer is reading
|
||||||
// ahead to the next period and the seek is to either the playing or reading period.
|
// ahead to the next period and the seek is to either the playing or reading period.
|
||||||
periodIndex = C.INDEX_UNSET;
|
periodIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the timeline, but keep the requested period if it is already prepared.
|
// Clear the timeline, but keep the requested period if it is already prepared.
|
||||||
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
|
||||||
MediaPeriodHolder newPlayingPeriodHolder = null;
|
MediaPeriodHolder<T> newPlayingPeriodHolder = null;
|
||||||
while (periodHolder != null) {
|
while (periodHolder != null) {
|
||||||
if (periodHolder.index == periodIndex && periodHolder.prepared) {
|
if (periodHolder.index == periodIndex && periodHolder.prepared) {
|
||||||
newPlayingPeriodHolder = periodHolder;
|
newPlayingPeriodHolder = periodHolder;
|
||||||
@ -672,7 +671,7 @@ import java.io.IOException;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Reselect tracks on each period in turn, until the selection changes.
|
// Reselect tracks on each period in turn, until the selection changes.
|
||||||
MediaPeriodHolder periodHolder = playingPeriodHolder;
|
MediaPeriodHolder<T> periodHolder = playingPeriodHolder;
|
||||||
boolean selectionsChangedForReadPeriod = true;
|
boolean selectionsChangedForReadPeriod = true;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (periodHolder == null || !periodHolder.prepared) {
|
if (periodHolder == null || !periodHolder.prepared) {
|
||||||
@ -691,16 +690,14 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectionsChangedForReadPeriod) {
|
if (selectionsChangedForReadPeriod) {
|
||||||
// Release everything after the playing period because a renderer may have read data from a
|
// Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
|
||||||
// track whose selection has now changed.
|
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
|
||||||
releasePeriodHoldersFrom(playingPeriodHolder.next);
|
releasePeriodHoldersFrom(playingPeriodHolder.next);
|
||||||
playingPeriodHolder.next = null;
|
playingPeriodHolder.next = null;
|
||||||
readingPeriodHolder = playingPeriodHolder;
|
readingPeriodHolder = playingPeriodHolder;
|
||||||
loadingPeriodHolder = playingPeriodHolder;
|
loadingPeriodHolder = playingPeriodHolder;
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
|
|
||||||
// Update streams for the new selection, recreating all streams if reading ahead.
|
|
||||||
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
|
|
||||||
boolean[] streamResetFlags = new boolean[renderers.length];
|
boolean[] streamResetFlags = new boolean[renderers.length];
|
||||||
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
|
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
|
||||||
playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
|
playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
|
||||||
@ -739,7 +736,7 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData);
|
trackSelector.onSelectionActivated(playingPeriodHolder.trackSelections);
|
||||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||||
} else {
|
} else {
|
||||||
// Release and re-prepare/buffer periods after the one whose selection changed.
|
// Release and re-prepare/buffer periods after the one whose selection changed.
|
||||||
@ -811,11 +808,11 @@ import java.io.IOException;
|
|||||||
playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window),
|
playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window),
|
||||||
index);
|
index);
|
||||||
|
|
||||||
MediaPeriodHolder previousPeriod = playingPeriodHolder;
|
MediaPeriodHolder<T> previousPeriodHolder = playingPeriodHolder;
|
||||||
boolean seenReadingPeriod = false;
|
boolean seenReadingPeriod = false;
|
||||||
bufferAheadPeriodCount = 0;
|
bufferAheadPeriodCount = 0;
|
||||||
while (previousPeriod.next != null) {
|
while (previousPeriodHolder.next != null) {
|
||||||
MediaPeriodHolder periodHolder = previousPeriod.next;
|
MediaPeriodHolder<T> periodHolder = previousPeriodHolder.next;
|
||||||
index++;
|
index++;
|
||||||
timeline.getPeriod(index, period, true);
|
timeline.getPeriod(index, period, true);
|
||||||
if (!periodHolder.uid.equals(period.uid)) {
|
if (!periodHolder.uid.equals(period.uid)) {
|
||||||
@ -836,7 +833,7 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the loading period to be the latest period that is still valid.
|
// Update the loading period to be the latest period that is still valid.
|
||||||
loadingPeriodHolder = previousPeriod;
|
loadingPeriodHolder = previousPeriodHolder;
|
||||||
loadingPeriodHolder.next = null;
|
loadingPeriodHolder.next = null;
|
||||||
|
|
||||||
// Release the rest of the timeline.
|
// Release the rest of the timeline.
|
||||||
@ -850,7 +847,7 @@ import java.io.IOException;
|
|||||||
if (periodHolder == readingPeriodHolder) {
|
if (periodHolder == readingPeriodHolder) {
|
||||||
seenReadingPeriod = true;
|
seenReadingPeriod = true;
|
||||||
}
|
}
|
||||||
previousPeriod = periodHolder;
|
previousPeriodHolder = periodHolder;
|
||||||
}
|
}
|
||||||
} else if (loadingPeriodHolder != null) {
|
} else if (loadingPeriodHolder != null) {
|
||||||
Object uid = loadingPeriodHolder.uid;
|
Object uid = loadingPeriodHolder.uid;
|
||||||
@ -953,10 +950,12 @@ import java.io.IOException;
|
|||||||
periodStartPositionUs = defaultPosition.second;
|
periodStartPositionUs = defaultPosition.second;
|
||||||
}
|
}
|
||||||
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
|
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
|
||||||
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this,
|
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex,
|
||||||
loadControl.getAllocator(), periodStartPositionUs);
|
loadControl.getAllocator(), periodStartPositionUs);
|
||||||
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
|
newMediaPeriod.prepare(this);
|
||||||
trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs);
|
MediaPeriodHolder<T> newPeriodHolder = new MediaPeriodHolder<>(renderers,
|
||||||
|
rendererCapabilities, trackSelector, mediaSource, newMediaPeriod, newPeriodUid,
|
||||||
|
periodStartPositionUs);
|
||||||
timeline.getWindow(windowIndex, window);
|
timeline.getWindow(windowIndex, window);
|
||||||
newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
|
newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
|
||||||
if (loadingPeriodHolder != null) {
|
if (loadingPeriodHolder != null) {
|
||||||
@ -995,19 +994,24 @@ import java.io.IOException;
|
|||||||
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
updateTimelineState();
|
updateTimelineState();
|
||||||
if (readingPeriodHolder == null) {
|
|
||||||
|
if (readingPeriodHolder.isLast) {
|
||||||
// The renderers have their final SampleStreams.
|
// The renderers have their final SampleStreams.
|
||||||
|
for (Renderer renderer : enabledRenderers) {
|
||||||
|
renderer.setCurrentStreamIsFinal();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
if (!renderer.hasReadStreamToEnd()) {
|
if (!renderer.hasReadStreamToEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
|
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
|
||||||
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
|
TrackSelections<T> oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||||
readingPeriodHolder = readingPeriodHolder.next;
|
readingPeriodHolder = readingPeriodHolder.next;
|
||||||
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections;
|
TrackSelections<T> newTrackSelections = readingPeriodHolder.trackSelections;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
TrackSelection oldSelection = oldTrackSelections.get(i);
|
TrackSelection oldSelection = oldTrackSelections.get(i);
|
||||||
@ -1029,11 +1033,6 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (readingPeriodHolder.isLast) {
|
|
||||||
readingPeriodHolder = null;
|
|
||||||
for (Renderer renderer : enabledRenderers) {
|
|
||||||
renderer.setCurrentStreamIsFinal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1071,7 +1070,7 @@ import java.io.IOException;
|
|||||||
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
||||||
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
||||||
long loadingPeriodPositionUs = rendererPositionUs
|
long loadingPeriodPositionUs = rendererPositionUs
|
||||||
- loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs;
|
- loadingPeriodHolder.rendererPositionOffsetUs;
|
||||||
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
||||||
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
|
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
|
||||||
setIsLoading(continueLoading);
|
setIsLoading(continueLoading);
|
||||||
@ -1086,14 +1085,15 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
|
private void releasePeriodHoldersFrom(MediaPeriodHolder<T> periodHolder) {
|
||||||
while (periodHolder != null) {
|
while (periodHolder != null) {
|
||||||
periodHolder.release();
|
periodHolder.release();
|
||||||
periodHolder = periodHolder.next;
|
periodHolder = periodHolder.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
|
private void setPlayingPeriodHolder(MediaPeriodHolder<T> periodHolder)
|
||||||
|
throws ExoPlaybackException {
|
||||||
int enabledRendererCount = 0;
|
int enabledRendererCount = 0;
|
||||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
@ -1116,7 +1116,7 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackSelector.onSelectionActivated(periodHolder.trackSelectionData);
|
trackSelector.onSelectionActivated(periodHolder.trackSelections);
|
||||||
playingPeriodHolder = periodHolder;
|
playingPeriodHolder = periodHolder;
|
||||||
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
|
||||||
}
|
}
|
||||||
@ -1173,7 +1173,7 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
|
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
|
||||||
*/
|
*/
|
||||||
private static final class MediaPeriodHolder {
|
private static final class MediaPeriodHolder<T> {
|
||||||
|
|
||||||
public final MediaPeriod mediaPeriod;
|
public final MediaPeriod mediaPeriod;
|
||||||
public final Object uid;
|
public final Object uid;
|
||||||
@ -1187,21 +1187,20 @@ import java.io.IOException;
|
|||||||
public boolean prepared;
|
public boolean prepared;
|
||||||
public boolean hasEnabledTracks;
|
public boolean hasEnabledTracks;
|
||||||
public long rendererPositionOffsetUs;
|
public long rendererPositionOffsetUs;
|
||||||
public MediaPeriodHolder next;
|
public MediaPeriodHolder<T> next;
|
||||||
public boolean needsContinueLoading;
|
public boolean needsContinueLoading;
|
||||||
|
|
||||||
private final Renderer[] renderers;
|
private final Renderer[] renderers;
|
||||||
private final RendererCapabilities[] rendererCapabilities;
|
private final RendererCapabilities[] rendererCapabilities;
|
||||||
private final TrackSelector trackSelector;
|
private final TrackSelector<T> trackSelector;
|
||||||
private final MediaSource mediaSource;
|
private final MediaSource mediaSource;
|
||||||
|
|
||||||
private Object trackSelectionData;
|
private TrackSelections<T> trackSelections;
|
||||||
private TrackSelectionArray trackSelections;
|
private TrackSelections<T> periodTrackSelections;
|
||||||
private TrackSelectionArray periodTrackSelections;
|
|
||||||
|
|
||||||
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||||
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid,
|
TrackSelector<T> trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod,
|
||||||
long positionUs) {
|
Object uid, long positionUs) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
@ -1213,7 +1212,7 @@ import java.io.IOException;
|
|||||||
startPositionUs = positionUs;
|
startPositionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNext(MediaPeriodHolder next) {
|
public void setNext(MediaPeriodHolder<T> next) {
|
||||||
this.next = next;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1235,14 +1234,12 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean selectTracks() throws ExoPlaybackException {
|
public boolean selectTracks() throws ExoPlaybackException {
|
||||||
Pair<TrackSelectionArray, Object> result =
|
TrackSelections<T> newTrackSelections = trackSelector.selectTracks(rendererCapabilities,
|
||||||
trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups());
|
mediaPeriod.getTrackGroups());
|
||||||
TrackSelectionArray newTrackSelections = result.first;
|
|
||||||
if (newTrackSelections.equals(periodTrackSelections)) {
|
if (newTrackSelections.equals(periodTrackSelections)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
trackSelections = newTrackSelections;
|
trackSelections = newTrackSelections;
|
||||||
trackSelectionData = result.second;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
/**
|
/**
|
||||||
* The version of the library, expressed as a string.
|
* The version of the library, expressed as a string.
|
||||||
*/
|
*/
|
||||||
String VERSION = "2.0.0";
|
String VERSION = "2.0.1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library, expressed as an integer.
|
* The version of the library, expressed as an integer.
|
||||||
@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
int VERSION_INT = 2000000;
|
int VERSION_INT = 2000001;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
@ -39,20 +39,6 @@ public final class Format implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final int NO_VALUE = -1;
|
public static final int NO_VALUE = -1;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the track should be selected if user preferences do not state otherwise.
|
|
||||||
*/
|
|
||||||
public static final int SELECTION_FLAG_DEFAULT = 1;
|
|
||||||
/**
|
|
||||||
* Indicates that the track must be displayed. Only applies to text tracks.
|
|
||||||
*/
|
|
||||||
public static final int SELECTION_FLAG_FORCED = 2;
|
|
||||||
/**
|
|
||||||
* Indicates that the player may choose to play the track in absence of an explicit user
|
|
||||||
* preference.
|
|
||||||
*/
|
|
||||||
public static final int SELECTION_FLAG_AUTOSELECT = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
|
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
|
||||||
* the timestamps of their parent samples.
|
* the timestamps of their parent samples.
|
||||||
@ -131,6 +117,7 @@ public final class Format implements Parcelable {
|
|||||||
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
|
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
|
||||||
* C#STEREO_MODE_LEFT_RIGHT}.
|
* C#STEREO_MODE_LEFT_RIGHT}.
|
||||||
*/
|
*/
|
||||||
|
@C.StereoMode
|
||||||
public final int stereoMode;
|
public final int stereoMode;
|
||||||
/**
|
/**
|
||||||
* The projection data for 360/VR video, or null if not applicable.
|
* The projection data for 360/VR video, or null if not applicable.
|
||||||
@ -153,6 +140,7 @@ public final class Format implements Parcelable {
|
|||||||
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
|
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
|
||||||
* other media types.
|
* other media types.
|
||||||
*/
|
*/
|
||||||
|
@C.PcmEncoding
|
||||||
public final int pcmEncoding;
|
public final int pcmEncoding;
|
||||||
/**
|
/**
|
||||||
* The number of samples to trim from the start of the decoded audio stream.
|
* The number of samples to trim from the start of the decoded audio stream.
|
||||||
@ -177,6 +165,7 @@ public final class Format implements Parcelable {
|
|||||||
/**
|
/**
|
||||||
* Track selection flags.
|
* Track selection flags.
|
||||||
*/
|
*/
|
||||||
|
@C.SelectionFlags
|
||||||
public final int selectionFlags;
|
public final int selectionFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,7 +207,7 @@ public final class Format implements Parcelable {
|
|||||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int maxInputSize, int width, int height, float frameRate,
|
int bitrate, int maxInputSize, int width, int height, float frameRate,
|
||||||
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
|
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
|
||||||
byte[] projectionData, int stereoMode, DrmInitData drmInitData) {
|
byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) {
|
||||||
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
|
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
|
||||||
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
|
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData,
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData,
|
||||||
@ -229,7 +218,7 @@ public final class Format implements Parcelable {
|
|||||||
|
|
||||||
public static Format createAudioContainerFormat(String id, String containerMimeType,
|
public static Format createAudioContainerFormat(String id, String containerMimeType,
|
||||||
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
|
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
|
||||||
List<byte[]> initializationData, int selectionFlags, String language) {
|
List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
|
||||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData,
|
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData,
|
||||||
@ -238,25 +227,26 @@ public final class Format implements Parcelable {
|
|||||||
|
|
||||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||||
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
|
List<byte[]> initializationData, DrmInitData drmInitData,
|
||||||
String language) {
|
@C.SelectionFlags int selectionFlags, String language) {
|
||||||
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
||||||
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
|
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
|
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||||
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags,
|
@C.PcmEncoding int pcmEncoding, List<byte[]> initializationData, DrmInitData drmInitData,
|
||||||
String language) {
|
@C.SelectionFlags int selectionFlags, String language) {
|
||||||
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
|
||||||
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
|
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
|
||||||
selectionFlags, language);
|
selectionFlags, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
|
int bitrate, int maxInputSize, int channelCount, int sampleRate,
|
||||||
int encoderDelay, int encoderPadding, List<byte[]> initializationData,
|
@C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding,
|
||||||
DrmInitData drmInitData, int selectionFlags, String language) {
|
List<byte[]> initializationData, DrmInitData drmInitData,
|
||||||
|
@C.SelectionFlags int selectionFlags, String language) {
|
||||||
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
|
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
||||||
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
|
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
|
||||||
@ -266,20 +256,21 @@ public final class Format implements Parcelable {
|
|||||||
// Text.
|
// Text.
|
||||||
|
|
||||||
public static Format createTextContainerFormat(String id, String containerMimeType,
|
public static Format createTextContainerFormat(String id, String containerMimeType,
|
||||||
String sampleMimeType, String codecs, int bitrate, int selectionFlags, String language) {
|
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
|
||||||
|
String language) {
|
||||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
|
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int selectionFlags, String language, DrmInitData drmInitData) {
|
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
|
||||||
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
|
||||||
drmInitData, OFFSET_SAMPLE_RELATIVE);
|
drmInitData, OFFSET_SAMPLE_RELATIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
|
||||||
int bitrate, int selectionFlags, String language, DrmInitData drmInitData,
|
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
|
||||||
long subsampleOffsetUs) {
|
long subsampleOffsetUs) {
|
||||||
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
@ -313,10 +304,10 @@ public final class Format implements Parcelable {
|
|||||||
|
|
||||||
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
|
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
|
||||||
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
||||||
float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount,
|
float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
|
||||||
int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags,
|
int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
|
||||||
String language, long subsampleOffsetUs, List<byte[]> initializationData,
|
int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
|
||||||
DrmInitData drmInitData) {
|
long subsampleOffsetUs, List<byte[]> initializationData, DrmInitData drmInitData) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.containerMimeType = containerMimeType;
|
this.containerMimeType = containerMimeType;
|
||||||
this.sampleMimeType = sampleMimeType;
|
this.sampleMimeType = sampleMimeType;
|
||||||
@ -343,6 +334,7 @@ public final class Format implements Parcelable {
|
|||||||
this.drmInitData = drmInitData;
|
this.drmInitData = drmInitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResourceType")
|
||||||
/* package */ Format(Parcel in) {
|
/* package */ Format(Parcel in) {
|
||||||
id = in.readString();
|
id = in.readString();
|
||||||
containerMimeType = in.readString();
|
containerMimeType = in.readString();
|
||||||
@ -388,8 +380,8 @@ public final class Format implements Parcelable {
|
|||||||
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
|
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
|
public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
|
||||||
int selectionFlags, String language) {
|
@C.SelectionFlags int selectionFlags, String language) {
|
||||||
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
|
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
|
||||||
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
|
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
|
||||||
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
|
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
|
||||||
@ -402,7 +394,7 @@ public final class Format implements Parcelable {
|
|||||||
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
|
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
|
||||||
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
|
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
|
||||||
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
|
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
|
||||||
int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
|
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
|
||||||
String language = this.language == null ? manifestFormat.language : this.language;
|
String language = this.language == null ? manifestFormat.language : this.language;
|
||||||
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|
||||||
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
|
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
|
||||||
|
@ -17,8 +17,7 @@ package com.google.android.exoplayer2;
|
|||||||
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,10 +30,10 @@ public interface LoadControl {
|
|||||||
*
|
*
|
||||||
* @param renderers The renderers.
|
* @param renderers The renderers.
|
||||||
* @param trackGroups The {@link TrackGroup}s from which the selection was made.
|
* @param trackGroups The {@link TrackGroup}s from which the selection was made.
|
||||||
* @param trackSelections The {@link TrackSelection}s that were made.
|
* @param trackSelections The track selections that were made.
|
||||||
*/
|
*/
|
||||||
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||||
TrackSelectionArray trackSelections);
|
TrackSelections<?> trackSelections);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the player when all tracks are disabled.
|
* Called by the player when all tracks are disabled.
|
||||||
|
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack;
|
|||||||
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
||||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||||
@ -40,6 +41,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
|||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.TextRenderer;
|
import com.google.android.exoplayer2.text.TextRenderer;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelections;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
@ -80,18 +82,14 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a frame is rendered for the first time since setting the surface, and when a
|
* Called when a frame is rendered for the first time since setting the surface, and when a
|
||||||
* frame is rendered for the first time since the renderer was reset.
|
* frame is rendered for the first time since a video track was selected.
|
||||||
*
|
|
||||||
* @param surface The {@link Surface} to which a first frame has been rendered.
|
|
||||||
*/
|
*/
|
||||||
void onRenderedFirstFrame(Surface surface);
|
void onRenderedFirstFrame();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the renderer is disabled.
|
* Called when a video track is no longer selected.
|
||||||
*
|
|
||||||
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
|
||||||
*/
|
*/
|
||||||
void onVideoDisabled(DecoderCounters counters);
|
void onVideoTracksDisabled();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
private final int videoRendererCount;
|
private final int videoRendererCount;
|
||||||
private final int audioRendererCount;
|
private final int audioRendererCount;
|
||||||
|
|
||||||
|
private boolean videoTracksEnabled;
|
||||||
private Format videoFormat;
|
private Format videoFormat;
|
||||||
private Format audioFormat;
|
private Format audioFormat;
|
||||||
|
|
||||||
|
private Surface surface;
|
||||||
private SurfaceHolder surfaceHolder;
|
private SurfaceHolder surfaceHolder;
|
||||||
private TextureView textureView;
|
private TextureView textureView;
|
||||||
private TextRenderer.Output textOutput;
|
private TextRenderer.Output textOutput;
|
||||||
@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
private float volume;
|
private float volume;
|
||||||
private PlaybackParamsHolder playbackParamsHolder;
|
private PlaybackParamsHolder playbackParamsHolder;
|
||||||
|
|
||||||
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
|
/* package */ SimpleExoPlayer(Context context, TrackSelector<?> trackSelector,
|
||||||
LoadControl loadControl, DrmSessionManager drmSessionManager,
|
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
|
||||||
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
|
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
|
||||||
mainHandler = new Handler();
|
mainHandler = new Handler();
|
||||||
componentListener = new ComponentListener();
|
componentListener = new ComponentListener();
|
||||||
|
trackSelector.addListener(componentListener);
|
||||||
|
|
||||||
// Build the renderers.
|
// Build the renderers.
|
||||||
ArrayList<Renderer> renderersList = new ArrayList<>();
|
ArrayList<Renderer> renderersList = new ArrayList<>();
|
||||||
@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void buildRenderers(Context context, DrmSessionManager drmSessionManager,
|
private void buildRenderers(Context context,
|
||||||
ArrayList<Renderer> renderersList, long allowedVideoJoiningTimeMs) {
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList,
|
||||||
|
long allowedVideoJoiningTimeMs) {
|
||||||
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
|
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
|
||||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
||||||
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener,
|
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener,
|
||||||
@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setVideoSurfaceInternal(Surface surface) {
|
private void setVideoSurfaceInternal(Surface surface) {
|
||||||
|
this.surface = surface;
|
||||||
ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount];
|
ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Renderer renderer : renderers) {
|
for (Renderer renderer : renderers) {
|
||||||
@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
private final class ComponentListener implements VideoRendererEventListener,
|
private final class ComponentListener implements VideoRendererEventListener,
|
||||||
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>,
|
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>,
|
||||||
SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
|
SurfaceHolder.Callback, TextureView.SurfaceTextureListener,
|
||||||
|
TrackSelector.EventListener<Object> {
|
||||||
|
|
||||||
// VideoRendererEventListener implementation
|
// VideoRendererEventListener implementation
|
||||||
|
|
||||||
@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRenderedFirstFrame(Surface surface) {
|
public void onRenderedFirstFrame(Surface surface) {
|
||||||
if (videoListener != null) {
|
if (videoListener != null && SimpleExoPlayer.this.surface == surface) {
|
||||||
videoListener.onRenderedFirstFrame(surface);
|
videoListener.onRenderedFirstFrame();
|
||||||
}
|
}
|
||||||
if (videoDebugListener != null) {
|
if (videoDebugListener != null) {
|
||||||
videoDebugListener.onRenderedFirstFrame(surface);
|
videoDebugListener.onRenderedFirstFrame(surface);
|
||||||
@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoDisabled(DecoderCounters counters) {
|
public void onVideoDisabled(DecoderCounters counters) {
|
||||||
if (videoListener != null) {
|
|
||||||
videoListener.onVideoDisabled(counters);
|
|
||||||
}
|
|
||||||
if (videoDebugListener != null) {
|
if (videoDebugListener != null) {
|
||||||
videoDebugListener.onVideoDisabled(counters);
|
videoDebugListener.onVideoDisabled(counters);
|
||||||
}
|
}
|
||||||
@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrackSelector.EventListener implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTrackSelectionsChanged(TrackSelections<?> trackSelections) {
|
||||||
|
boolean videoTracksEnabled = false;
|
||||||
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
|
if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelections.get(i) != null) {
|
||||||
|
videoTracksEnabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (videoListener != null && SimpleExoPlayer.this.videoTracksEnabled && !videoTracksEnabled) {
|
||||||
|
videoListener.onVideoTracksDisabled();
|
||||||
|
}
|
||||||
|
SimpleExoPlayer.this.videoTracksEnabled = videoTracksEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
|
@ -208,7 +208,9 @@ public final class AudioTrack {
|
|||||||
private android.media.AudioTrack audioTrack;
|
private android.media.AudioTrack audioTrack;
|
||||||
private int sampleRate;
|
private int sampleRate;
|
||||||
private int channelConfig;
|
private int channelConfig;
|
||||||
|
@C.Encoding
|
||||||
private int sourceEncoding;
|
private int sourceEncoding;
|
||||||
|
@C.Encoding
|
||||||
private int targetEncoding;
|
private int targetEncoding;
|
||||||
private boolean passthrough;
|
private boolean passthrough;
|
||||||
private int pcmFrameSize;
|
private int pcmFrameSize;
|
||||||
@ -348,8 +350,8 @@ public final class AudioTrack {
|
|||||||
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
|
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
|
||||||
* suitable buffer size automatically.
|
* suitable buffer size automatically.
|
||||||
*/
|
*/
|
||||||
public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding,
|
public void configure(String mimeType, int channelCount, int sampleRate,
|
||||||
int specifiedBufferSize) {
|
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) {
|
||||||
int channelConfig;
|
int channelConfig;
|
||||||
switch (channelCount) {
|
switch (channelCount) {
|
||||||
case 1:
|
case 1:
|
||||||
@ -381,7 +383,7 @@ public final class AudioTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
||||||
int sourceEncoding;
|
@C.Encoding int sourceEncoding;
|
||||||
if (passthrough) {
|
if (passthrough) {
|
||||||
sourceEncoding = getEncodingForMimeType(mimeType);
|
sourceEncoding = getEncodingForMimeType(mimeType);
|
||||||
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|
||||||
@ -470,7 +472,7 @@ public final class AudioTrack {
|
|||||||
if (keepSessionIdAudioTrack == null) {
|
if (keepSessionIdAudioTrack == null) {
|
||||||
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
|
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
|
||||||
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||||
int encoding = C.ENCODING_PCM_16BIT;
|
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
|
||||||
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
|
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
|
||||||
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
|
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
|
||||||
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
|
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
|
||||||
@ -962,7 +964,7 @@ public final class AudioTrack {
|
|||||||
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
|
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
|
||||||
* capacity was insufficient for the output.
|
* capacity was insufficient for the output.
|
||||||
*/
|
*/
|
||||||
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int sourceEncoding,
|
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding,
|
||||||
ByteBuffer out) {
|
ByteBuffer out) {
|
||||||
int offset = buffer.position();
|
int offset = buffer.position();
|
||||||
int limit = buffer.limit();
|
int limit = buffer.limit();
|
||||||
@ -1023,6 +1025,7 @@ public final class AudioTrack {
|
|||||||
return resampledBuffer;
|
return resampledBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@C.Encoding
|
||||||
private static int getEncodingForMimeType(String mimeType) {
|
private static int getEncodingForMimeType(String mimeType) {
|
||||||
switch (mimeType) {
|
switch (mimeType) {
|
||||||
case MimeTypes.AUDIO_AC3:
|
case MimeTypes.AUDIO_AC3:
|
||||||
@ -1038,7 +1041,7 @@ public final class AudioTrack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) {
|
private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) {
|
||||||
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
|
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
|
||||||
return DtsUtil.parseDtsAudioSampleCount(buffer);
|
return DtsUtil.parseDtsAudioSampleCount(buffer);
|
||||||
} else if (encoding == C.ENCODING_AC3) {
|
} else if (encoding == C.ENCODING_AC3) {
|
||||||
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
*/
|
*/
|
||||||
public abstract class Buffer {
|
public abstract class Buffer {
|
||||||
|
|
||||||
|
@C.BufferFlags
|
||||||
private int flags;
|
private int flags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +59,7 @@ public abstract class Buffer {
|
|||||||
* @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
|
* @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
|
||||||
* constants.
|
* constants.
|
||||||
*/
|
*/
|
||||||
public final void setFlags(int flags) {
|
public final void setFlags(@C.BufferFlags int flags) {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ public abstract class Buffer {
|
|||||||
* @param flag The flag to add to this buffer's flags, which should be one of the
|
* @param flag The flag to add to this buffer's flags, which should be one of the
|
||||||
* {@code C.BUFFER_FLAG_*} constants.
|
* {@code C.BUFFER_FLAG_*} constants.
|
||||||
*/
|
*/
|
||||||
public final void addFlag(int flag) {
|
public final void addFlag(@C.BufferFlags int flag) {
|
||||||
flags |= flag;
|
flags |= flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ public abstract class Buffer {
|
|||||||
*
|
*
|
||||||
* @param flag The flag to remove.
|
* @param flag The flag to remove.
|
||||||
*/
|
*/
|
||||||
public final void clearFlag(int flag) {
|
public final void clearFlag(@C.BufferFlags int flag) {
|
||||||
flags &= ~flag;
|
flags &= ~flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ public abstract class Buffer {
|
|||||||
* @param flag The flag to check.
|
* @param flag The flag to check.
|
||||||
* @return Whether the flag is set.
|
* @return Whether the flag is set.
|
||||||
*/
|
*/
|
||||||
protected final boolean getFlag(int flag) {
|
protected final boolean getFlag(@C.BufferFlags int flag) {
|
||||||
return (flags & flag) == flag;
|
return (flags & flag) == flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.decoder;
|
package com.google.android.exoplayer2.decoder;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,6 +35,7 @@ public final class CryptoInfo {
|
|||||||
/**
|
/**
|
||||||
* @see android.media.MediaCodec.CryptoInfo#mode
|
* @see android.media.MediaCodec.CryptoInfo#mode
|
||||||
*/
|
*/
|
||||||
|
@C.CryptoMode
|
||||||
public int mode;
|
public int mode;
|
||||||
/**
|
/**
|
||||||
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
|
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
|
||||||
@ -58,7 +60,7 @@ public final class CryptoInfo {
|
|||||||
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
|
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
|
||||||
*/
|
*/
|
||||||
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
|
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
|
||||||
byte[] key, byte[] iv, int mode) {
|
byte[] key, byte[] iv, @C.CryptoMode int mode) {
|
||||||
this.numSubSamples = numSubSamples;
|
this.numSubSamples = numSubSamples;
|
||||||
this.numBytesOfClearData = numBytesOfClearData;
|
this.numBytesOfClearData = numBytesOfClearData;
|
||||||
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
|
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
|
||||||
|
@ -15,7 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.decoder;
|
package com.google.android.exoplayer2.decoder;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,16 +26,21 @@ import java.nio.ByteBuffer;
|
|||||||
*/
|
*/
|
||||||
public class DecoderInputBuffer extends Buffer {
|
public class DecoderInputBuffer extends Buffer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buffer replacement mode, which may disable replacement.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL,
|
||||||
|
BUFFER_REPLACEMENT_MODE_DIRECT})
|
||||||
|
public @interface BufferReplacementMode {}
|
||||||
/**
|
/**
|
||||||
* Disallows buffer replacement.
|
* Disallows buffer replacement.
|
||||||
*/
|
*/
|
||||||
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
|
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
|
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
|
||||||
*/
|
*/
|
||||||
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
|
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
|
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
|
||||||
*/
|
*/
|
||||||
@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer {
|
|||||||
*/
|
*/
|
||||||
public long timeUs;
|
public long timeUs;
|
||||||
|
|
||||||
|
@BufferReplacementMode
|
||||||
private final int bufferReplacementMode;
|
private final int bufferReplacementMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +69,7 @@ public class DecoderInputBuffer extends Buffer {
|
|||||||
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
|
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
|
||||||
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
|
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
|
||||||
*/
|
*/
|
||||||
public DecoderInputBuffer(int bufferReplacementMode) {
|
public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) {
|
||||||
this.cryptoInfo = new CryptoInfo();
|
this.cryptoInfo = new CryptoInfo();
|
||||||
this.bufferReplacementMode = bufferReplacementMode;
|
this.bufferReplacementMode = bufferReplacementMode;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
package com.google.android.exoplayer2.drm;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DRM session.
|
* A DRM session.
|
||||||
@ -23,6 +26,12 @@ import android.annotation.TargetApi;
|
|||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
public interface DrmSession<T extends ExoMediaCrypto> {
|
public interface DrmSession<T extends ExoMediaCrypto> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the DRM session.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
|
||||||
|
@interface State {}
|
||||||
/**
|
/**
|
||||||
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
|
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
|
||||||
*/
|
*/
|
||||||
@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
|
|||||||
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
|
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
|
||||||
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
|
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
|
||||||
*/
|
*/
|
||||||
|
@State
|
||||||
int getState();
|
int getState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
|
|||||||
/**
|
/**
|
||||||
* Releases a {@link DrmSession}.
|
* Releases a {@link DrmSession}.
|
||||||
*/
|
*/
|
||||||
void releaseSession(DrmSession drmSession);
|
void releaseSession(DrmSession<T> drmSession);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import java.util.UUID;
|
|||||||
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
|
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
|
||||||
*/
|
*/
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager,
|
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
|
||||||
DrmSession<T> {
|
DrmSession<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,6 +87,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
|||||||
|
|
||||||
private int openCount;
|
private int openCount;
|
||||||
private boolean provisioningInProgress;
|
private boolean provisioningInProgress;
|
||||||
|
@DrmSession.State
|
||||||
private int state;
|
private int state;
|
||||||
private T mediaCrypto;
|
private T mediaCrypto;
|
||||||
private Exception lastException;
|
private Exception lastException;
|
||||||
@ -267,7 +268,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSession(DrmSession session) {
|
public void releaseSession(DrmSession<T> session) {
|
||||||
if (--openCount != 0) {
|
if (--openCount != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -291,6 +292,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
|
|||||||
// DrmSession implementation.
|
// DrmSession implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@DrmSession.State
|
||||||
public final int getState() {
|
public final int getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.drm;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when the requested DRM scheme is not supported.
|
* Thrown when the requested DRM scheme is not supported.
|
||||||
*/
|
*/
|
||||||
public final class UnsupportedDrmException extends Exception {
|
public final class UnsupportedDrmException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason for the exception.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
|
||||||
|
public @interface Reason {}
|
||||||
/**
|
/**
|
||||||
* The requested DRM scheme is unsupported by the device.
|
* The requested DRM scheme is unsupported by the device.
|
||||||
*/
|
*/
|
||||||
@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception {
|
|||||||
/**
|
/**
|
||||||
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||||
*/
|
*/
|
||||||
|
@Reason
|
||||||
public final int reason;
|
public final int reason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||||
*/
|
*/
|
||||||
public UnsupportedDrmException(int reason) {
|
public UnsupportedDrmException(@Reason int reason) {
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception {
|
|||||||
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
|
||||||
* @param cause The cause of this exception.
|
* @param cause The cause of this exception.
|
||||||
*/
|
*/
|
||||||
public UnsupportedDrmException(int reason, Exception cause) {
|
public UnsupportedDrmException(@Reason int reason, Exception cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput {
|
|||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
ensureSpaceForPeek(length);
|
ensureSpaceForPeek(length);
|
||||||
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
|
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
|
||||||
peekBufferLength += length - bytesPeeked;
|
|
||||||
while (bytesPeeked < length) {
|
while (bytesPeeked < length) {
|
||||||
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
|
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
|
||||||
allowEndOfInput);
|
allowEndOfInput);
|
||||||
@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
peekBufferPosition += length;
|
peekBufferPosition += length;
|
||||||
|
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
long offset = extrasHolder.offset;
|
long offset = extrasHolder.offset;
|
||||||
|
|
||||||
// Read the signal byte.
|
// Read the signal byte.
|
||||||
|
scratch.reset(1);
|
||||||
readData(offset, scratch.data, 1);
|
readData(offset, scratch.data, 1);
|
||||||
offset++;
|
offset++;
|
||||||
byte signalByte = scratch.data[0];
|
byte signalByte = scratch.data[0];
|
||||||
@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
// Read the subsample count, if present.
|
// Read the subsample count, if present.
|
||||||
int subsampleCount;
|
int subsampleCount;
|
||||||
if (subsampleEncryption) {
|
if (subsampleEncryption) {
|
||||||
|
scratch.reset(2);
|
||||||
readData(offset, scratch.data, 2);
|
readData(offset, scratch.data, 2);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
scratch.setPosition(0);
|
|
||||||
subsampleCount = scratch.readUnsignedShort();
|
subsampleCount = scratch.readUnsignedShort();
|
||||||
} else {
|
} else {
|
||||||
subsampleCount = 1;
|
subsampleCount = 1;
|
||||||
@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
if (subsampleEncryption) {
|
if (subsampleEncryption) {
|
||||||
int subsampleDataLength = 6 * subsampleCount;
|
int subsampleDataLength = 6 * subsampleCount;
|
||||||
ensureCapacity(scratch, subsampleDataLength);
|
scratch.reset(subsampleDataLength);
|
||||||
readData(offset, scratch.data, subsampleDataLength);
|
readData(offset, scratch.data, subsampleDataLength);
|
||||||
offset += subsampleDataLength;
|
offset += subsampleDataLength;
|
||||||
scratch.setPosition(0);
|
scratch.setPosition(0);
|
||||||
@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that the passed {@link ParsableByteArray} is of at least the specified limit.
|
|
||||||
*/
|
|
||||||
private static void ensureCapacity(ParsableByteArray byteArray, int limit) {
|
|
||||||
if (byteArray.limit() < limit) {
|
|
||||||
byteArray.reset(new byte[limit], limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the loading thread.
|
// Called by the loading thread.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||||
|
byte[] encryptionKey) {
|
||||||
if (!startWriteOperation()) {
|
if (!startWriteOperation()) {
|
||||||
infoQueue.commitSampleTimestamp(timeUs);
|
infoQueue.commitSampleTimestamp(timeUs);
|
||||||
return;
|
return;
|
||||||
@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
|
public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
|
||||||
byte[] encryptionKey) {
|
int size, byte[] encryptionKey) {
|
||||||
Assertions.checkState(!upstreamFormatRequired);
|
Assertions.checkState(!upstreamFormatRequired);
|
||||||
commitSampleTimestamp(timeUs);
|
commitSampleTimestamp(timeUs);
|
||||||
timesUs[relativeWriteIndex] = timeUs;
|
timesUs[relativeWriteIndex] = timeUs;
|
||||||
|
@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||||
|
byte[] encryptionKey) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ public interface TrackOutput {
|
|||||||
* whose metadata is being passed.
|
* whose metadata is being passed.
|
||||||
* @param encryptionKey The encryption key associated with the sample. May be null.
|
* @param encryptionKey The encryption key associated with the sample. May be null.
|
||||||
*/
|
*/
|
||||||
void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey);
|
void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||||
|
byte[] encryptionKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
|
private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
|
||||||
private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
|
private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
|
||||||
private static final int ID_LANGUAGE = 0x22B59C;
|
private static final int ID_LANGUAGE = 0x22B59C;
|
||||||
|
private static final int ID_PROJECTION = 0x7670;
|
||||||
|
private static final int ID_PROJECTION_PRIVATE = 0x7672;
|
||||||
|
private static final int ID_STEREO_MODE = 0x53B8;
|
||||||
|
|
||||||
private static final int LACING_NONE = 0;
|
private static final int LACING_NONE = 0;
|
||||||
private static final int LACING_XIPH = 1;
|
private static final int LACING_XIPH = 1;
|
||||||
@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
private int[] blockLacingSampleSizes;
|
private int[] blockLacingSampleSizes;
|
||||||
private int blockTrackNumber;
|
private int blockTrackNumber;
|
||||||
private int blockTrackNumberLength;
|
private int blockTrackNumberLength;
|
||||||
|
@C.BufferFlags
|
||||||
private int blockFlags;
|
private int blockFlags;
|
||||||
|
|
||||||
// Sample reading state.
|
// Sample reading state.
|
||||||
@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
case ID_CUE_POINT:
|
case ID_CUE_POINT:
|
||||||
case ID_CUE_TRACK_POSITIONS:
|
case ID_CUE_TRACK_POSITIONS:
|
||||||
case ID_BLOCK_GROUP:
|
case ID_BLOCK_GROUP:
|
||||||
|
case ID_PROJECTION:
|
||||||
return EbmlReader.TYPE_MASTER;
|
return EbmlReader.TYPE_MASTER;
|
||||||
case ID_EBML_READ_VERSION:
|
case ID_EBML_READ_VERSION:
|
||||||
case ID_DOC_TYPE_READ_VERSION:
|
case ID_DOC_TYPE_READ_VERSION:
|
||||||
@ -390,6 +395,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
case ID_CUE_TIME:
|
case ID_CUE_TIME:
|
||||||
case ID_CUE_CLUSTER_POSITION:
|
case ID_CUE_CLUSTER_POSITION:
|
||||||
case ID_REFERENCE_BLOCK:
|
case ID_REFERENCE_BLOCK:
|
||||||
|
case ID_STEREO_MODE:
|
||||||
return EbmlReader.TYPE_UNSIGNED_INT;
|
return EbmlReader.TYPE_UNSIGNED_INT;
|
||||||
case ID_DOC_TYPE:
|
case ID_DOC_TYPE:
|
||||||
case ID_CODEC_ID:
|
case ID_CODEC_ID:
|
||||||
@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
case ID_SIMPLE_BLOCK:
|
case ID_SIMPLE_BLOCK:
|
||||||
case ID_BLOCK:
|
case ID_BLOCK:
|
||||||
case ID_CODEC_PRIVATE:
|
case ID_CODEC_PRIVATE:
|
||||||
|
case ID_PROJECTION_PRIVATE:
|
||||||
return EbmlReader.TYPE_BINARY;
|
return EbmlReader.TYPE_BINARY;
|
||||||
case ID_DURATION:
|
case ID_DURATION:
|
||||||
case ID_SAMPLING_FREQUENCY:
|
case ID_SAMPLING_FREQUENCY:
|
||||||
@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
case ID_BLOCK_DURATION:
|
case ID_BLOCK_DURATION:
|
||||||
blockDurationUs = scaleTimecodeToUs(value);
|
blockDurationUs = scaleTimecodeToUs(value);
|
||||||
return;
|
return;
|
||||||
|
case ID_STEREO_MODE:
|
||||||
|
int layout = (int) value;
|
||||||
|
switch (layout) {
|
||||||
|
case 0:
|
||||||
|
currentTrack.stereoMode = C.STEREO_MODE_MONO;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
currentTrack.codecPrivate = new byte[contentSize];
|
currentTrack.codecPrivate = new byte[contentSize];
|
||||||
input.readFully(currentTrack.codecPrivate, 0, contentSize);
|
input.readFully(currentTrack.codecPrivate, 0, contentSize);
|
||||||
return;
|
return;
|
||||||
|
case ID_PROJECTION_PRIVATE:
|
||||||
|
currentTrack.projectionData = new byte[contentSize];
|
||||||
|
input.readFully(currentTrack.projectionData, 0, contentSize);
|
||||||
|
return;
|
||||||
case ID_CONTENT_COMPRESSION_SETTINGS:
|
case ID_CONTENT_COMPRESSION_SETTINGS:
|
||||||
// This extractor only supports header stripping, so the payload is the stripped bytes.
|
// This extractor only supports header stripping, so the payload is the stripped bytes.
|
||||||
currentTrack.sampleStrippedBytes = new byte[contentSize];
|
currentTrack.sampleStrippedBytes = new byte[contentSize];
|
||||||
@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
samplePartitionCountRead = true;
|
samplePartitionCountRead = true;
|
||||||
}
|
}
|
||||||
int samplePartitionDataSize = samplePartitionCount * 4;
|
int samplePartitionDataSize = samplePartitionCount * 4;
|
||||||
if (scratch.limit() < samplePartitionDataSize) {
|
scratch.reset(samplePartitionDataSize);
|
||||||
scratch.reset(new byte[samplePartitionDataSize], samplePartitionDataSize);
|
|
||||||
}
|
|
||||||
input.readFully(scratch.data, 0, samplePartitionDataSize);
|
input.readFully(scratch.data, 0, samplePartitionDataSize);
|
||||||
sampleBytesRead += samplePartitionDataSize;
|
sampleBytesRead += samplePartitionDataSize;
|
||||||
scratch.setPosition(0);
|
|
||||||
scratch.setLimit(samplePartitionDataSize);
|
|
||||||
short subsampleCount = (short) (1 + (samplePartitionCount / 2));
|
short subsampleCount = (short) (1 + (samplePartitionCount / 2));
|
||||||
int subsampleDataSize = 2 + 6 * subsampleCount;
|
int subsampleDataSize = 2 + 6 * subsampleCount;
|
||||||
if (encryptionSubsampleDataBuffer == null
|
if (encryptionSubsampleDataBuffer == null
|
||||||
@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
public int displayWidth = Format.NO_VALUE;
|
public int displayWidth = Format.NO_VALUE;
|
||||||
public int displayHeight = Format.NO_VALUE;
|
public int displayHeight = Format.NO_VALUE;
|
||||||
public int displayUnit = DISPLAY_UNIT_PIXELS;
|
public int displayUnit = DISPLAY_UNIT_PIXELS;
|
||||||
|
public byte[] projectionData = null;
|
||||||
|
@C.StereoMode
|
||||||
|
public int stereoMode = Format.NO_VALUE;
|
||||||
|
|
||||||
// Audio elements. Initially set to their default values.
|
// Audio elements. Initially set to their default values.
|
||||||
public int channelCount = 1;
|
public int channelCount = 1;
|
||||||
@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
||||||
String mimeType;
|
String mimeType;
|
||||||
int maxInputSize = Format.NO_VALUE;
|
int maxInputSize = Format.NO_VALUE;
|
||||||
int pcmEncoding = Format.NO_VALUE;
|
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
|
||||||
List<byte[]> initializationData = null;
|
List<byte[]> initializationData = null;
|
||||||
switch (codecId) {
|
switch (codecId) {
|
||||||
case CODEC_ID_VP8:
|
case CODEC_ID_VP8:
|
||||||
@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Format format;
|
Format format;
|
||||||
int selectionFlags = 0;
|
@C.SelectionFlags int selectionFlags = 0;
|
||||||
selectionFlags |= flagDefault ? Format.SELECTION_FLAG_DEFAULT : 0;
|
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
|
||||||
selectionFlags |= flagForced ? Format.SELECTION_FLAG_FORCED : 0;
|
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
|
||||||
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
|
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
|
||||||
// into the trackId passed when creating the formats.
|
// into the trackId passed when creating the formats.
|
||||||
if (MimeTypes.isAudio(mimeType)) {
|
if (MimeTypes.isAudio(mimeType)) {
|
||||||
@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
|
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||||
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
|
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
|
||||||
Format.NO_VALUE, pixelWidthHeightRatio, drmInitData);
|
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData);
|
||||||
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
||||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||||
Format.NO_VALUE, selectionFlags, language, drmInitData);
|
Format.NO_VALUE, selectionFlags, language, drmInitData);
|
||||||
|
@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
@Override
|
@Override
|
||||||
public int read(ExtractorInput input, PositionHolder seekPosition)
|
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) {
|
if (synchronizedHeaderData == 0) {
|
||||||
return RESULT_END_OF_INPUT;
|
try {
|
||||||
|
synchronize(input, false);
|
||||||
|
} catch (EOFException e) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (seeker == null) {
|
if (seeker == null) {
|
||||||
seeker = setupSeeker(input);
|
seeker = setupSeeker(input);
|
||||||
@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
|
|
||||||
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
|
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
|
||||||
if (sampleBytesRemaining == 0) {
|
if (sampleBytesRemaining == 0) {
|
||||||
if (!maybeResynchronize(extractorInput)) {
|
extractorInput.resetPeekPosition();
|
||||||
|
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
|
||||||
return RESULT_END_OF_INPUT;
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
|
scratch.setPosition(0);
|
||||||
|
int sampleHeaderData = scratch.readInt();
|
||||||
|
if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK)
|
||||||
|
|| MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
|
||||||
|
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
|
||||||
|
extractorInput.skipFully(1);
|
||||||
|
synchronizedHeaderData = 0;
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
|
||||||
if (basisTimeUs == C.TIME_UNSET) {
|
if (basisTimeUs == C.TIME_UNSET) {
|
||||||
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
|
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
|
||||||
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
|
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
|
||||||
@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary.
|
|
||||||
*/
|
|
||||||
private boolean maybeResynchronize(ExtractorInput extractorInput)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
extractorInput.resetPeekPosition();
|
|
||||||
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
scratch.setPosition(0);
|
|
||||||
int sampleHeaderData = scratch.readInt();
|
|
||||||
if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) {
|
|
||||||
int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData);
|
|
||||||
if (frameSize != C.LENGTH_UNSET) {
|
|
||||||
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronizedHeaderData = 0;
|
|
||||||
extractorInput.skipFully(1);
|
|
||||||
return synchronizeCatchingEndOfInput(extractorInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean synchronizeCatchingEndOfInput(ExtractorInput input)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
// An EOFException will be raised if any peek operation was partially satisfied. If a seek
|
|
||||||
// operation resulted in reading from within the last frame, we may try to peek past the end of
|
|
||||||
// the file in a partially-satisfied read operation, so we need to catch the exception.
|
|
||||||
try {
|
|
||||||
return synchronize(input, false);
|
|
||||||
} catch (EOFException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean synchronize(ExtractorInput input, boolean sniffing)
|
private boolean synchronize(ExtractorInput input, boolean sniffing)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
int searched = 0;
|
|
||||||
int validFrameCount = 0;
|
int validFrameCount = 0;
|
||||||
int candidateSynchronizedHeaderData = 0;
|
int candidateSynchronizedHeaderData = 0;
|
||||||
int peekedId3Bytes = 0;
|
int peekedId3Bytes = 0;
|
||||||
|
int searchedBytes = 0;
|
||||||
|
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
|
||||||
input.resetPeekPosition();
|
input.resetPeekPosition();
|
||||||
if (input.getPosition() == 0) {
|
if (input.getPosition() == 0) {
|
||||||
Id3Util.parseId3(input, gaplessInfoHolder);
|
Id3Util.parseId3(input, gaplessInfoHolder);
|
||||||
@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
if (sniffing && searched == MAX_SNIFF_BYTES) {
|
if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
|
||||||
return false;
|
// We reached the end of the stream but found at least one valid frame.
|
||||||
}
|
break;
|
||||||
if (!sniffing && searched == MAX_SYNC_BYTES) {
|
|
||||||
throw new ParserException("Searched too many bytes.");
|
|
||||||
}
|
|
||||||
if (!input.peekFully(scratch.data, 0, 4, true)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
scratch.setPosition(0);
|
scratch.setPosition(0);
|
||||||
int headerData = scratch.readInt();
|
int headerData = scratch.readInt();
|
||||||
@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
if ((candidateSynchronizedHeaderData != 0
|
if ((candidateSynchronizedHeaderData != 0
|
||||||
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|
||||||
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
|
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
|
||||||
// The header is invalid or doesn't match the candidate header. Try the next byte offset.
|
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
|
||||||
|
if (searchedBytes++ == searchLimitBytes) {
|
||||||
|
if (!sniffing) {
|
||||||
|
throw new ParserException("Searched too many bytes.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
validFrameCount = 0;
|
validFrameCount = 0;
|
||||||
candidateSynchronizedHeaderData = 0;
|
candidateSynchronizedHeaderData = 0;
|
||||||
searched++;
|
|
||||||
if (sniffing) {
|
if (sniffing) {
|
||||||
input.resetPeekPosition();
|
input.resetPeekPosition();
|
||||||
input.advancePeekPosition(peekedId3Bytes + searched);
|
input.advancePeekPosition(peekedId3Bytes + searchedBytes);
|
||||||
} else {
|
} else {
|
||||||
input.skipFully(1);
|
input.skipFully(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The header is valid and matches the candidate header.
|
// The header matches the candidate header and/or is valid.
|
||||||
validFrameCount++;
|
validFrameCount++;
|
||||||
if (validFrameCount == 1) {
|
if (validFrameCount == 1) {
|
||||||
MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
|
MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
|
||||||
@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
// Prepare to read the synchronized frame.
|
// Prepare to read the synchronized frame.
|
||||||
if (sniffing) {
|
if (sniffing) {
|
||||||
input.skipFully(peekedId3Bytes + searched);
|
input.skipFully(peekedId3Bytes + searchedBytes);
|
||||||
} else {
|
} else {
|
||||||
input.resetPeekPosition();
|
input.resetPeekPosition();
|
||||||
}
|
}
|
||||||
@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
|
|
||||||
long position = input.getPosition();
|
long position = input.getPosition();
|
||||||
long length = input.getLength();
|
long length = input.getLength();
|
||||||
|
int headerData = 0;
|
||||||
|
Seeker seeker = null;
|
||||||
|
|
||||||
// Check if there is a Xing header.
|
// Check if there is a Xing header.
|
||||||
int xingBase = (synchronizedHeader.version & 1) != 0
|
int xingBase = (synchronizedHeader.version & 1) != 0
|
||||||
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
|
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
|
||||||
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
|
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
|
||||||
frame.setPosition(xingBase);
|
if (frame.limit() >= xingBase + 4) {
|
||||||
int headerData = frame.readInt();
|
frame.setPosition(xingBase);
|
||||||
Seeker seeker = null;
|
headerData = frame.readInt();
|
||||||
|
}
|
||||||
if (headerData == XING_HEADER || headerData == INFO_HEADER) {
|
if (headerData == XING_HEADER || headerData == INFO_HEADER) {
|
||||||
seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
|
seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
|
||||||
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
|
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
|
||||||
@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
|
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
|
||||||
}
|
}
|
||||||
input.skipFully(synchronizedHeader.frameSize);
|
input.skipFully(synchronizedHeader.frameSize);
|
||||||
} else {
|
} else if (frame.limit() >= 40) {
|
||||||
// Check if there is a VBRI header.
|
// Check if there is a VBRI header.
|
||||||
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
|
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
|
||||||
headerData = frame.readInt();
|
headerData = frame.readInt();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.mp4;
|
package com.google.android.exoplayer2.extractor.mp4;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
@ -38,6 +39,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class AtomParsers {
|
/* package */ final class AtomParsers {
|
||||||
|
|
||||||
|
private static final String TAG = "AtomParsers";
|
||||||
|
|
||||||
private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
|
private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
|
||||||
private static final int TYPE_soun = Util.getIntegerCodeForString("soun");
|
private static final int TYPE_soun = Util.getIntegerCodeForString("soun");
|
||||||
private static final int TYPE_text = Util.getIntegerCodeForString("text");
|
private static final int TYPE_text = Util.getIntegerCodeForString("text");
|
||||||
@ -248,11 +251,16 @@ import java.util.List;
|
|||||||
remainingTimestampOffsetChanges--;
|
remainingTimestampOffsetChanges--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all the expected samples have been seen.
|
// If the stbl's child boxes are not consistent the container is malformed, but the stream may
|
||||||
Assertions.checkArgument(remainingSynchronizationSamples == 0);
|
// still be playable.
|
||||||
Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0);
|
if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0
|
||||||
Assertions.checkArgument(remainingSamplesInChunk == 0);
|
|| remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) {
|
||||||
Assertions.checkArgument(remainingTimestampDeltaChanges == 0);
|
Log.w(TAG, "Inconsistent stbl box for track " + track.id
|
||||||
|
+ ": remainingSynchronizationSamples " + remainingSynchronizationSamples
|
||||||
|
+ ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta
|
||||||
|
+ ", remainingSamplesInChunk " + remainingSamplesInChunk
|
||||||
|
+ ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
long[] chunkOffsetsBytes = new long[chunkIterator.length];
|
long[] chunkOffsetsBytes = new long[chunkIterator.length];
|
||||||
int[] chunkSampleCounts = new int[chunkIterator.length];
|
int[] chunkSampleCounts = new int[chunkIterator.length];
|
||||||
@ -636,7 +644,7 @@ import java.util.List;
|
|||||||
0 /* subsample timing is absolute */);
|
0 /* subsample timing is absolute */);
|
||||||
} else if (childAtomType == Atom.TYPE_c608) {
|
} else if (childAtomType == Atom.TYPE_c608) {
|
||||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, 0, language, drmInitData);
|
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData);
|
||||||
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
|
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
|
||||||
}
|
}
|
||||||
stsd.setPosition(childStartPosition + childAtomSize);
|
stsd.setPosition(childStartPosition + childAtomSize);
|
||||||
@ -665,6 +673,7 @@ import java.util.List;
|
|||||||
List<byte[]> initializationData = null;
|
List<byte[]> initializationData = null;
|
||||||
String mimeType = null;
|
String mimeType = null;
|
||||||
byte[] projectionData = null;
|
byte[] projectionData = null;
|
||||||
|
@C.StereoMode
|
||||||
int stereoMode = Format.NO_VALUE;
|
int stereoMode = Format.NO_VALUE;
|
||||||
while (childPosition - position < size) {
|
while (childPosition - position < size) {
|
||||||
parent.setPosition(childPosition);
|
parent.setPosition(childPosition);
|
||||||
@ -889,7 +898,7 @@ import java.util.List;
|
|||||||
|
|
||||||
if (out.format == null && mimeType != null) {
|
if (out.format == null && mimeType != null) {
|
||||||
// TODO: Determine the correct PCM encoding.
|
// TODO: Determine the correct PCM encoding.
|
||||||
int pcmEncoding =
|
@C.PcmEncoding int pcmEncoding =
|
||||||
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
|
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
|
||||||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
||||||
@ -1169,6 +1178,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public Format format;
|
public Format format;
|
||||||
public int nalUnitLengthFieldLength;
|
public int nalUnitLengthFieldLength;
|
||||||
|
@Track.Transformation
|
||||||
public int requiredSampleTransformation;
|
public int requiredSampleTransformation;
|
||||||
|
|
||||||
public StsdData(int numberOfEntries) {
|
public StsdData(int numberOfEntries) {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.mp4;
|
package com.google.android.exoplayer2.extractor.mp4;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
|
|||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -64,6 +67,13 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
private static final String TAG = "FragmentedMp4Extractor";
|
private static final String TAG = "FragmentedMp4Extractor";
|
||||||
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
|
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags controlling the behavior of the extractor.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
|
||||||
|
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED})
|
||||||
|
public @interface Flags {}
|
||||||
/**
|
/**
|
||||||
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
|
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
|
||||||
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
|
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
|
||||||
@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* This flag does nothing if the stream is not a video stream.
|
* This flag does nothing if the stream is not a video stream.
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to ignore any tfdt boxes in the stream.
|
* Flag to ignore any tfdt boxes in the stream.
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
|
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
|
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
|
||||||
* container.
|
* container.
|
||||||
@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
|
private static final int STATE_READING_SAMPLE_CONTINUE = 4;
|
||||||
|
|
||||||
// Workarounds.
|
// Workarounds.
|
||||||
|
@Flags
|
||||||
private final int flags;
|
private final int flags;
|
||||||
private final Track sideloadedTrack;
|
private final Track sideloadedTrack;
|
||||||
|
|
||||||
@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param flags Flags to allow parsing of faulty streams.
|
* @param flags Flags that control the extractor's behavior.
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(int flags) {
|
public FragmentedMp4Extractor(@Flags int flags) {
|
||||||
this(flags, null);
|
this(flags, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param flags Flags to allow parsing of faulty streams.
|
* @param flags Flags that control the extractor's behavior.
|
||||||
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
|
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
|
||||||
* will not receive a moov box in the input data.
|
* will not receive a moov box in the input data.
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(int flags, Track sideloadedTrack) {
|
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) {
|
||||||
this.sideloadedTrack = sideloadedTrack;
|
this.sideloadedTrack = sideloadedTrack;
|
||||||
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
||||||
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
|
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
|
||||||
@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
|
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
|
||||||
int flags, byte[] extendedTypeScratch) throws ParserException {
|
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
int moofContainerChildrenSize = moof.containerChildren.size();
|
int moofContainerChildrenSize = moof.containerChildren.size();
|
||||||
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
||||||
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
||||||
@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* Parses a traf atom (defined in 14496-12).
|
* Parses a traf atom (defined in 14496-12).
|
||||||
*/
|
*/
|
||||||
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
||||||
int flags, byte[] extendedTypeScratch) throws ParserException {
|
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||||
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
||||||
if (trackBundle == null) {
|
if (trackBundle == null) {
|
||||||
@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
|
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
|
||||||
int flags) {
|
@Flags int flags) {
|
||||||
int trunCount = 0;
|
int trunCount = 0;
|
||||||
int totalSampleCount = 0;
|
int totalSampleCount = 0;
|
||||||
List<LeafAtom> leafChildren = traf.leafChildren;
|
List<LeafAtom> leafChildren = traf.leafChildren;
|
||||||
@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param trun The trun atom to decode.
|
* @param trun The trun atom to decode.
|
||||||
* @return The starting position of samples for the next run.
|
* @return The starting position of samples for the next run.
|
||||||
*/
|
*/
|
||||||
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags,
|
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
|
||||||
ParsableByteArray trun, int trackRunStart) {
|
@Flags int flags, ParsableByteArray trun, int trackRunStart) {
|
||||||
trun.setPosition(Atom.HEADER_SIZE);
|
trun.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = trun.readInt();
|
int fullAtom = trun.readInt();
|
||||||
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
|
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||||
int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
|
@C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
|
||||||
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
|
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
|
||||||
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
|
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
|
||||||
byte[] encryptionKey = null;
|
byte[] encryptionKey = null;
|
||||||
|
@ -100,13 +100,14 @@ import java.io.IOException;
|
|||||||
while (bytesSearched < bytesToSearch) {
|
while (bytesSearched < bytesToSearch) {
|
||||||
// Read an atom header.
|
// Read an atom header.
|
||||||
int headerSize = Atom.HEADER_SIZE;
|
int headerSize = Atom.HEADER_SIZE;
|
||||||
|
buffer.reset(headerSize);
|
||||||
input.peekFully(buffer.data, 0, headerSize);
|
input.peekFully(buffer.data, 0, headerSize);
|
||||||
buffer.setPosition(0);
|
|
||||||
long atomSize = buffer.readUnsignedInt();
|
long atomSize = buffer.readUnsignedInt();
|
||||||
int atomType = buffer.readInt();
|
int atomType = buffer.readInt();
|
||||||
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||||
headerSize = Atom.LONG_HEADER_SIZE;
|
headerSize = Atom.LONG_HEADER_SIZE;
|
||||||
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
|
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
|
||||||
|
buffer.setLimit(Atom.LONG_HEADER_SIZE);
|
||||||
atomSize = buffer.readUnsignedLongToLong();
|
atomSize = buffer.readUnsignedLongToLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,9 +140,7 @@ import java.io.IOException;
|
|||||||
if (atomDataSize < 8) {
|
if (atomDataSize < 8) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (buffer.capacity() < atomDataSize) {
|
buffer.reset(atomDataSize);
|
||||||
buffer.reset(new byte[atomDataSize], atomDataSize);
|
|
||||||
}
|
|
||||||
input.peekFully(buffer.data, 0, atomDataSize);
|
input.peekFully(buffer.data, 0, atomDataSize);
|
||||||
int brandsCount = atomDataSize / 4;
|
int brandsCount = atomDataSize / 4;
|
||||||
for (int i = 0; i < brandsCount; i++) {
|
for (int i = 0; i < brandsCount; i++) {
|
||||||
|
@ -15,14 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.mp4;
|
package com.google.android.exoplayer2.extractor.mp4;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates information describing an MP4 track.
|
* Encapsulates information describing an MP4 track.
|
||||||
*/
|
*/
|
||||||
public final class Track {
|
public final class Track {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transformation to apply to samples in the track, if any.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})
|
||||||
|
public @interface Transformation {}
|
||||||
/**
|
/**
|
||||||
* A no-op sample transformation.
|
* A no-op sample transformation.
|
||||||
*/
|
*/
|
||||||
@ -66,6 +75,7 @@ public final class Track {
|
|||||||
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
|
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
|
||||||
* sample.
|
* sample.
|
||||||
*/
|
*/
|
||||||
|
@Transformation
|
||||||
public final int sampleTransformation;
|
public final int sampleTransformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +100,7 @@ public final class Track {
|
|||||||
public final int nalUnitLengthFieldLength;
|
public final int nalUnitLengthFieldLength;
|
||||||
|
|
||||||
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
|
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
|
||||||
Format format, int sampleTransformation,
|
Format format, @Transformation int sampleTransformation,
|
||||||
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
|
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
|
||||||
long[] editListDurations, long[] editListMediaTimes) {
|
long[] editListDurations, long[] editListMediaTimes) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
*/
|
*/
|
||||||
public final int[] flags;
|
public final int[] flags;
|
||||||
|
|
||||||
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
|
public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs,
|
||||||
|
int[] flags) {
|
||||||
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
||||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||||
|
@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts EIA-608 data from a RawCC file
|
* Extracts CEA data from a RawCC file.
|
||||||
*/
|
*/
|
||||||
public final class RawCcExtractor implements Extractor {
|
public final class RawCcExtractor implements Extractor {
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor {
|
|||||||
trackOutput = extractorOutput.track(0);
|
trackOutput = extractorOutput.track(0);
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
|
|
||||||
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608,
|
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
|
||||||
null, Format.NO_VALUE, 0, null, null));
|
null, Format.NO_VALUE, 0, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor {
|
|||||||
dataScratch.reset();
|
dataScratch.reset();
|
||||||
input.readFully(dataScratch.data, 0, 3);
|
input.readFully(dataScratch.data, 0, 3);
|
||||||
|
|
||||||
// only accept EIA-608 packets which have validity (6th bit) == 1 and
|
trackOutput.sampleData(dataScratch, 3);
|
||||||
// type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100
|
sampleBytesWritten += 3;
|
||||||
int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07;
|
|
||||||
if (ccValidityAndType == 0x04) {
|
|
||||||
trackOutput.sampleData(dataScratch, 2);
|
|
||||||
sampleBytesWritten += 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sampleBytesWritten > 0) {
|
if (sampleBytesWritten > 0) {
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import android.util.SparseBooleanArray;
|
||||||
|
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation for {@link ElementaryStreamReader.Factory}.
|
||||||
|
*/
|
||||||
|
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags controlling what workarounds are enabled for elementary stream readers.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM,
|
||||||
|
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE})
|
||||||
|
public @interface WorkaroundFlags {
|
||||||
|
}
|
||||||
|
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
||||||
|
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
||||||
|
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
||||||
|
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
|
||||||
|
public static final int WORKAROUND_MAP_BY_TYPE = 16;
|
||||||
|
|
||||||
|
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1.
|
||||||
|
|
||||||
|
private final SparseBooleanArray trackIds;
|
||||||
|
@WorkaroundFlags
|
||||||
|
private final int workaroundFlags;
|
||||||
|
private Id3Reader id3Reader;
|
||||||
|
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
|
||||||
|
|
||||||
|
public DefaultStreamReaderFactory() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultStreamReaderFactory(int workaroundFlags) {
|
||||||
|
trackIds = new SparseBooleanArray();
|
||||||
|
this.workaroundFlags = workaroundFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
|
||||||
|
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
|
||||||
|
|
||||||
|
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
|
||||||
|
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
||||||
|
// appears intermittently during playback. See b/20261500.
|
||||||
|
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
|
||||||
|
}
|
||||||
|
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
|
||||||
|
if (trackIds.get(trackId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
trackIds.put(trackId, true);
|
||||||
|
switch (streamType) {
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_MPA:
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
||||||
|
return new MpegAudioReader(output.track(trackId), esInfo.language);
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_AAC:
|
||||||
|
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
|
||||||
|
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_AC3:
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
||||||
|
return new Ac3Reader(output.track(trackId), esInfo.language);
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_DTS:
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
||||||
|
return new DtsReader(output.track(trackId), esInfo.language);
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_H262:
|
||||||
|
return new H262Reader(output.track(trackId));
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_H264:
|
||||||
|
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0
|
||||||
|
? null : new H264Reader(output.track(trackId),
|
||||||
|
new SeiReader(output.track(nextEmbeddedTrackId++)),
|
||||||
|
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
||||||
|
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_H265:
|
||||||
|
return new H265Reader(output.track(trackId),
|
||||||
|
new SeiReader(output.track(nextEmbeddedTrackId++)));
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_ID3:
|
||||||
|
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
|
||||||
|
return id3Reader;
|
||||||
|
} else {
|
||||||
|
return new Id3Reader(output.track(nextEmbeddedTrackId++));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,13 +15,60 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts individual samples from an elementary media stream, preserving original order.
|
* Extracts individual samples from an elementary media stream, preserving original order.
|
||||||
*/
|
*/
|
||||||
/* package */ abstract class ElementaryStreamReader {
|
public abstract class ElementaryStreamReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory of {@link ElementaryStreamReader} instances.
|
||||||
|
*/
|
||||||
|
public interface Factory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the
|
||||||
|
* stream type is not supported or if the stream already has a reader assigned to it.
|
||||||
|
*
|
||||||
|
* @param pid The pid for the PMT entry.
|
||||||
|
* @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining
|
||||||
|
* the type of the stream.
|
||||||
|
* @param esInfo The descriptor information linked to the elementary stream.
|
||||||
|
* @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the
|
||||||
|
* created readers.
|
||||||
|
* @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided
|
||||||
|
* pid. {@code null} if the stream is not supported or if it should be ignored.
|
||||||
|
*/
|
||||||
|
ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo,
|
||||||
|
ExtractorOutput output);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds descriptor information associated with an elementary stream.
|
||||||
|
*/
|
||||||
|
public static final class EsInfo {
|
||||||
|
|
||||||
|
public final int streamType;
|
||||||
|
public String language;
|
||||||
|
public byte[] descriptorBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param streamType The type of the stream as defined by the
|
||||||
|
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
|
||||||
|
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
|
||||||
|
* @param descriptorBytes The descriptor bytes associated to the stream.
|
||||||
|
*/
|
||||||
|
public EsInfo(int streamType, String language, byte[] descriptorBytes) {
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.language = language;
|
||||||
|
this.descriptorBytes = descriptorBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
protected final TrackOutput output;
|
protected final TrackOutput output;
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ import java.util.Collections;
|
|||||||
if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) {
|
if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) {
|
||||||
int bytesWrittenPastStartCode = limit - startCodeOffset;
|
int bytesWrittenPastStartCode = limit - startCodeOffset;
|
||||||
if (foundFirstFrameInGroup) {
|
if (foundFirstFrameInGroup) {
|
||||||
int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
@C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||||
int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode;
|
int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode;
|
||||||
output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null);
|
output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null);
|
||||||
isKeyframe = false;
|
isKeyframe = false;
|
||||||
@ -136,7 +136,7 @@ import java.util.Collections;
|
|||||||
if (startCodeValue == START_GROUP) {
|
if (startCodeValue == START_GROUP) {
|
||||||
foundFirstFrameInGroup = false;
|
foundFirstFrameInGroup = false;
|
||||||
isKeyframe = true;
|
isKeyframe = true;
|
||||||
} else /* startCode == START_PICTURE */ {
|
} else /* startCodeValue == START_PICTURE */ {
|
||||||
frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs);
|
frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs);
|
||||||
framePosition = totalBytesWritten - bytesWrittenPastStartCode;
|
framePosition = totalBytesWritten - bytesWrittenPastStartCode;
|
||||||
pesPtsUsAvailable = false;
|
pesPtsUsAvailable = false;
|
||||||
|
@ -57,7 +57,7 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param output A {@link TrackOutput} to which H.264 samples should be written.
|
* @param output A {@link TrackOutput} to which H.264 samples should be written.
|
||||||
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
|
||||||
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
|
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
|
||||||
* synchronization samples (key-frames).
|
* synchronization samples (key-frames).
|
||||||
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
|
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
|
||||||
@ -420,7 +420,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void outputSample(int offset) {
|
private void outputSample(int offset) {
|
||||||
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||||
int size = (int) (nalUnitStartPosition - samplePosition);
|
int size = (int) (nalUnitStartPosition - samplePosition);
|
||||||
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ import java.util.Collections;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param output A {@link TrackOutput} to which H.265 samples should be written.
|
* @param output A {@link TrackOutput} to which H.265 samples should be written.
|
||||||
* @param seiReader A reader for EIA-608 samples in SEI NAL units.
|
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
|
||||||
*/
|
*/
|
||||||
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
public H265Reader(TrackOutput output, SeiReader seiReader) {
|
||||||
super(output);
|
super(output);
|
||||||
@ -471,7 +471,7 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void outputSample(int offset) {
|
private void outputSample(int offset) {
|
||||||
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
@C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||||
int size = (int) (nalUnitStartPosition - samplePosition);
|
int size = (int) (nalUnitStartPosition - samplePosition);
|
||||||
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
|
||||||
}
|
}
|
||||||
|
@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor {
|
|||||||
input.peekFully(psPacketBuffer.data, 0, 10);
|
input.peekFully(psPacketBuffer.data, 0, 10);
|
||||||
|
|
||||||
// We only care about the pack_stuffing_length in here, skip the first 77 bits.
|
// We only care about the pack_stuffing_length in here, skip the first 77 bits.
|
||||||
psPacketBuffer.setPosition(0);
|
psPacketBuffer.setPosition(9);
|
||||||
psPacketBuffer.skipBytes(9);
|
|
||||||
|
|
||||||
// Last 3 bits is the length.
|
// Last 3 bits is the length.
|
||||||
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
|
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
|
||||||
@ -209,7 +208,7 @@ public final class PsExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next 2 bytes are the length, once we have that we can consume the complete packet.
|
// The next 2 bytes are the length. Once we have that we can consume the complete packet.
|
||||||
input.peekFully(psPacketBuffer.data, 0, 2);
|
input.peekFully(psPacketBuffer.data, 0, 2);
|
||||||
psPacketBuffer.setPosition(0);
|
psPacketBuffer.setPosition(0);
|
||||||
int payloadLength = psPacketBuffer.readUnsignedShort();
|
int payloadLength = psPacketBuffer.readUnsignedShort();
|
||||||
@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor {
|
|||||||
// Just skip this data.
|
// Just skip this data.
|
||||||
input.skipFully(pesLength);
|
input.skipFully(pesLength);
|
||||||
} else {
|
} else {
|
||||||
if (psPacketBuffer.capacity() < pesLength) {
|
psPacketBuffer.reset(pesLength);
|
||||||
// Reallocate for this and future packets.
|
|
||||||
psPacketBuffer.reset(new byte[pesLength], pesLength);
|
|
||||||
}
|
|
||||||
// Read the whole packet and the header for consumption.
|
// Read the whole packet and the header for consumption.
|
||||||
input.readFully(psPacketBuffer.data, 0, pesLength);
|
input.readFully(psPacketBuffer.data, 0, pesLength);
|
||||||
psPacketBuffer.setPosition(6);
|
psPacketBuffer.setPosition(6);
|
||||||
psPacketBuffer.setLimit(pesLength);
|
|
||||||
payloadReader.consume(psPacketBuffer);
|
payloadReader.consume(psPacketBuffer);
|
||||||
psPacketBuffer.setLimit(psPacketBuffer.capacity());
|
psPacketBuffer.setLimit(psPacketBuffer.capacity());
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.text.eia608.Eia608Decoder;
|
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}.
|
* Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
|
||||||
*/
|
*/
|
||||||
/* package */ final class SeiReader {
|
/* package */ final class SeiReader {
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, null,
|
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
|
||||||
Format.NO_VALUE, 0, null, null));
|
Format.NO_VALUE, 0, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
payloadSize += b;
|
payloadSize += b;
|
||||||
} while (b == 0xFF);
|
} while (b == 0xFF);
|
||||||
// Process the payload.
|
// Process the payload.
|
||||||
if (Eia608Decoder.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
|
||||||
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
|
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
|
||||||
// + user_data_type_code (1).
|
// + user_data_type_code (1).
|
||||||
seiBuffer.skipBytes(8);
|
seiBuffer.skipBytes(8);
|
||||||
@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
seiBuffer.skipBytes(1);
|
seiBuffer.skipBytes(1);
|
||||||
int sampleBytes = 0;
|
int sampleBytes = 0;
|
||||||
for (int i = 0; i < ccCount; i++) {
|
for (int i = 0; i < ccCount; i++) {
|
||||||
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07;
|
int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07;
|
||||||
// Check that validity == 1 and type == 0.
|
// Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1).
|
||||||
if (ccValidityAndType != 0x04) {
|
if (ccValidityAndType != 0x04) {
|
||||||
seiBuffer.skipBytes(2);
|
seiBuffer.skipBytes(3);
|
||||||
} else {
|
} else {
|
||||||
sampleBytes += 2;
|
sampleBytes += 3;
|
||||||
output.sampleData(seiBuffer, 2);
|
output.sampleData(seiBuffer, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);
|
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);
|
||||||
|
@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseBooleanArray;
|
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
@ -32,6 +30,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
|
|||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||||
@ -50,30 +49,24 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
|
|
||||||
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
|
|
||||||
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
|
|
||||||
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
|
|
||||||
public static final int WORKAROUND_MAP_BY_TYPE = 16;
|
|
||||||
|
|
||||||
private static final String TAG = "TsExtractor";
|
private static final String TAG = "TsExtractor";
|
||||||
|
|
||||||
private static final int TS_PACKET_SIZE = 188;
|
private static final int TS_PACKET_SIZE = 188;
|
||||||
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
|
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
|
||||||
private static final int TS_PAT_PID = 0;
|
private static final int TS_PAT_PID = 0;
|
||||||
|
|
||||||
private static final int TS_STREAM_TYPE_MPA = 0x03;
|
public static final int TS_STREAM_TYPE_MPA = 0x03;
|
||||||
private static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
|
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
|
||||||
private static final int TS_STREAM_TYPE_AAC = 0x0F;
|
public static final int TS_STREAM_TYPE_AAC = 0x0F;
|
||||||
private static final int TS_STREAM_TYPE_AC3 = 0x81;
|
public static final int TS_STREAM_TYPE_AC3 = 0x81;
|
||||||
private static final int TS_STREAM_TYPE_DTS = 0x8A;
|
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
||||||
private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
||||||
private static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
||||||
private static final int TS_STREAM_TYPE_H262 = 0x02;
|
public static final int TS_STREAM_TYPE_H262 = 0x02;
|
||||||
private static final int TS_STREAM_TYPE_H264 = 0x1B;
|
public static final int TS_STREAM_TYPE_H264 = 0x1B;
|
||||||
private static final int TS_STREAM_TYPE_H265 = 0x24;
|
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
||||||
private static final int TS_STREAM_TYPE_ID3 = 0x15;
|
public static final int TS_STREAM_TYPE_ID3 = 0x15;
|
||||||
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1
|
|
||||||
|
|
||||||
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
|
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
|
||||||
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
|
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
|
||||||
@ -83,35 +76,38 @@ public final class TsExtractor implements Extractor {
|
|||||||
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
|
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
|
||||||
|
|
||||||
private final TimestampAdjuster timestampAdjuster;
|
private final TimestampAdjuster timestampAdjuster;
|
||||||
private final int workaroundFlags;
|
|
||||||
private final ParsableByteArray tsPacketBuffer;
|
private final ParsableByteArray tsPacketBuffer;
|
||||||
private final ParsableBitArray tsScratch;
|
private final ParsableBitArray tsScratch;
|
||||||
private final SparseIntArray continuityCounters;
|
private final SparseIntArray continuityCounters;
|
||||||
|
private final ElementaryStreamReader.Factory streamReaderFactory;
|
||||||
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||||
/* package */ final SparseBooleanArray trackIds;
|
|
||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private ExtractorOutput output;
|
private ExtractorOutput output;
|
||||||
private int nextEmbeddedTrackId;
|
|
||||||
/* package */ Id3Reader id3Reader;
|
|
||||||
|
|
||||||
public TsExtractor() {
|
public TsExtractor() {
|
||||||
this(new TimestampAdjuster(0));
|
this(new TimestampAdjuster(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||||
|
*/
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
||||||
this(timestampAdjuster, 0);
|
this(timestampAdjuster, new DefaultStreamReaderFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster, int workaroundFlags) {
|
/**
|
||||||
|
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||||
|
* @param customReaderFactory Factory for injecting a custom set of elementary stream readers.
|
||||||
|
*/
|
||||||
|
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
||||||
|
ElementaryStreamReader.Factory customReaderFactory) {
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
this.workaroundFlags = workaroundFlags;
|
this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
|
||||||
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
||||||
tsScratch = new ParsableBitArray(new byte[3]);
|
tsScratch = new ParsableBitArray(new byte[3]);
|
||||||
tsPayloadReaders = new SparseArray<>();
|
tsPayloadReaders = new SparseArray<>();
|
||||||
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
|
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
|
||||||
trackIds = new SparseBooleanArray();
|
|
||||||
nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
|
|
||||||
continuityCounters = new SparseIntArray();
|
continuityCounters = new SparseIntArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,12 +412,6 @@ public final class TsExtractor implements Extractor {
|
|||||||
// Skip the descriptors.
|
// Skip the descriptors.
|
||||||
sectionData.skipBytes(programInfoLength);
|
sectionData.skipBytes(programInfoLength);
|
||||||
|
|
||||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
|
|
||||||
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
|
||||||
// appears intermittently during playback. See b/20261500.
|
|
||||||
id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3));
|
|
||||||
}
|
|
||||||
|
|
||||||
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
|
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
|
||||||
- programInfoLength - 4 /* CRC length */;
|
- programInfoLength - 4 /* CRC length */;
|
||||||
while (remainingEntriesLength > 0) {
|
while (remainingEntriesLength > 0) {
|
||||||
@ -431,63 +421,15 @@ public final class TsExtractor implements Extractor {
|
|||||||
int elementaryPid = pmtScratch.readBits(13);
|
int elementaryPid = pmtScratch.readBits(13);
|
||||||
pmtScratch.skipBits(4); // reserved
|
pmtScratch.skipBits(4); // reserved
|
||||||
int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
|
int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
|
||||||
EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
|
ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
|
||||||
if (streamType == 0x06) {
|
if (streamType == 0x06) {
|
||||||
streamType = esInfo.streamType;
|
streamType = esInfo.streamType;
|
||||||
}
|
}
|
||||||
remainingEntriesLength -= esInfoLength + 5;
|
remainingEntriesLength -= esInfoLength + 5;
|
||||||
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid;
|
ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid,
|
||||||
if (trackIds.get(trackId)) {
|
streamType, esInfo, output);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ElementaryStreamReader pesPayloadReader;
|
|
||||||
switch (streamType) {
|
|
||||||
case TS_STREAM_TYPE_MPA:
|
|
||||||
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_MPA_LSF:
|
|
||||||
pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_AAC:
|
|
||||||
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
|
|
||||||
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_AC3:
|
|
||||||
case TS_STREAM_TYPE_E_AC3:
|
|
||||||
pesPayloadReader = new Ac3Reader(output.track(trackId), esInfo.language);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_DTS:
|
|
||||||
case TS_STREAM_TYPE_HDMV_DTS:
|
|
||||||
pesPayloadReader = new DtsReader(output.track(trackId), esInfo.language);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_H262:
|
|
||||||
pesPayloadReader = new H262Reader(output.track(trackId));
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_H264:
|
|
||||||
pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
|
|
||||||
: new H264Reader(output.track(trackId),
|
|
||||||
new SeiReader(output.track(nextEmbeddedTrackId++)),
|
|
||||||
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
|
||||||
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_H265:
|
|
||||||
pesPayloadReader = new H265Reader(output.track(trackId),
|
|
||||||
new SeiReader(output.track(nextEmbeddedTrackId++)));
|
|
||||||
break;
|
|
||||||
case TS_STREAM_TYPE_ID3:
|
|
||||||
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
|
|
||||||
pesPayloadReader = id3Reader;
|
|
||||||
} else {
|
|
||||||
pesPayloadReader = new Id3Reader(output.track(nextEmbeddedTrackId++));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
pesPayloadReader = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pesPayloadReader != null) {
|
if (pesPayloadReader != null) {
|
||||||
trackIds.put(trackId, true);
|
|
||||||
tsPayloadReaders.put(elementaryPid,
|
tsPayloadReaders.put(elementaryPid,
|
||||||
new PesReader(pesPayloadReader, timestampAdjuster));
|
new PesReader(pesPayloadReader, timestampAdjuster));
|
||||||
}
|
}
|
||||||
@ -497,18 +439,17 @@ public final class TsExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stream info read from the available descriptors, or -1 if no
|
* Returns the stream info read from the available descriptors. Sets {@code data}'s position to
|
||||||
* descriptors are present. Sets {@code data}'s position to the end of the descriptors.
|
* the end of the descriptors.
|
||||||
*
|
*
|
||||||
* @param data A buffer with its position set to the start of the first descriptor.
|
* @param data A buffer with its position set to the start of the first descriptor.
|
||||||
* @param length The length of descriptors to read from the current position in {@code data}.
|
* @param length The length of descriptors to read from the current position in {@code data}.
|
||||||
* @return The stream info read from the available descriptors, or -1 if no
|
* @return The stream info read from the available descriptors.
|
||||||
* descriptors are present.
|
|
||||||
*/
|
*/
|
||||||
private EsInfo readEsInfo(ParsableByteArray data, int length) {
|
private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) {
|
||||||
int descriptorsEndPosition = data.getPosition() + length;
|
int descriptorsStartPosition = data.getPosition();
|
||||||
|
int descriptorsEndPosition = descriptorsStartPosition + length;
|
||||||
int streamType = -1;
|
int streamType = -1;
|
||||||
int audioType = -1;
|
|
||||||
String language = null;
|
String language = null;
|
||||||
while (data.getPosition() < descriptorsEndPosition) {
|
while (data.getPosition() < descriptorsEndPosition) {
|
||||||
int descriptorTag = data.readUnsignedByte();
|
int descriptorTag = data.readUnsignedByte();
|
||||||
@ -531,27 +472,14 @@ public final class TsExtractor implements Extractor {
|
|||||||
streamType = TS_STREAM_TYPE_DTS;
|
streamType = TS_STREAM_TYPE_DTS;
|
||||||
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
||||||
language = new String(data.data, data.getPosition(), 3).trim();
|
language = new String(data.data, data.getPosition(), 3).trim();
|
||||||
audioType = data.data[data.getPosition() + 3];
|
// Audio type is ignored.
|
||||||
}
|
}
|
||||||
// Skip unused bytes of current descriptor.
|
// Skip unused bytes of current descriptor.
|
||||||
data.skipBytes(positionOfNextDescriptor - data.getPosition());
|
data.skipBytes(positionOfNextDescriptor - data.getPosition());
|
||||||
}
|
}
|
||||||
data.setPosition(descriptorsEndPosition);
|
data.setPosition(descriptorsEndPosition);
|
||||||
return new EsInfo(streamType, audioType, language);
|
return new ElementaryStreamReader.EsInfo(streamType, language,
|
||||||
}
|
Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
|
||||||
|
|
||||||
private final class EsInfo {
|
|
||||||
|
|
||||||
final int streamType;
|
|
||||||
final int audioType;
|
|
||||||
final String language;
|
|
||||||
|
|
||||||
public EsInfo(int streamType, int audioType, String language) {
|
|
||||||
this.streamType = streamType;
|
|
||||||
this.audioType = audioType;
|
|
||||||
this.language = language;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
/** Bits per sample for the audio data. */
|
/** Bits per sample for the audio data. */
|
||||||
private final int bitsPerSample;
|
private final int bitsPerSample;
|
||||||
/** The PCM encoding */
|
/** The PCM encoding */
|
||||||
|
@C.PcmEncoding
|
||||||
private final int encoding;
|
private final int encoding;
|
||||||
|
|
||||||
/** Offset to the start of sample data. */
|
/** Offset to the start of sample data. */
|
||||||
@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
private long dataSize;
|
private long dataSize;
|
||||||
|
|
||||||
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
|
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
|
||||||
int bitsPerSample, int encoding) {
|
int bitsPerSample, @C.PcmEncoding int encoding) {
|
||||||
this.numChannels = numChannels;
|
this.numChannels = numChannels;
|
||||||
this.sampleRateHz = sampleRateHz;
|
this.sampleRateHz = sampleRateHz;
|
||||||
this.averageBytesPerSecond = averageBytesPerSecond;
|
this.averageBytesPerSecond = averageBytesPerSecond;
|
||||||
@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the PCM encoding. **/
|
/** Returns the PCM encoding. **/
|
||||||
|
@C.PcmEncoding
|
||||||
public int getEncoding() {
|
public int getEncoding() {
|
||||||
return encoding;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ import java.io.IOException;
|
|||||||
+ blockAlignment);
|
+ blockAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
int encoding = Util.getPcmEncoding(bitsPerSample);
|
@C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample);
|
||||||
if (encoding == C.ENCODING_INVALID) {
|
if (encoding == C.ENCODING_INVALID) {
|
||||||
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
|
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
|
||||||
return null;
|
return null;
|
||||||
|
@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
MediaCrypto mediaCrypto = null;
|
MediaCrypto mediaCrypto = null;
|
||||||
boolean drmSessionRequiresSecureDecoder = false;
|
boolean drmSessionRequiresSecureDecoder = false;
|
||||||
if (drmSession != null) {
|
if (drmSession != null) {
|
||||||
int drmSessionState = drmSession.getState();
|
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
} else if (drmSessionState == DrmSession.STATE_OPENED
|
||||||
@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
if (drmSession == null) {
|
if (drmSession == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int drmSessionState = drmSession.getState();
|
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
int sourceIndex = timeline.getSourceIndexForPeriod(index);
|
int sourceIndex = timeline.getSourceIndexForPeriod(index);
|
||||||
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
|
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, callback,
|
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator,
|
||||||
allocator, positionUs);
|
positionUs);
|
||||||
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
|
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
|
||||||
return mediaPeriod;
|
return mediaPeriod;
|
||||||
}
|
}
|
||||||
|
@ -61,12 +61,15 @@ import java.util.Arrays;
|
|||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final ExtractorMediaSource.EventListener eventListener;
|
private final ExtractorMediaSource.EventListener eventListener;
|
||||||
private final MediaSource.Listener sourceListener;
|
private final MediaSource.Listener sourceListener;
|
||||||
private final Callback callback;
|
|
||||||
private final Allocator allocator;
|
private final Allocator allocator;
|
||||||
private final Loader loader;
|
private final Loader loader;
|
||||||
private final ExtractorHolder extractorHolder;
|
private final ExtractorHolder extractorHolder;
|
||||||
private final ConditionVariable loadCondition;
|
private final ConditionVariable loadCondition;
|
||||||
|
private final Runnable maybeFinishPrepareRunnable;
|
||||||
|
private final Runnable onContinueLoadingRequestedRunnable;
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private SeekMap seekMap;
|
private SeekMap seekMap;
|
||||||
private boolean tracksBuilt;
|
private boolean tracksBuilt;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
@ -85,6 +88,7 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
private int extractedSamplesCountAtStartOfLoad;
|
private int extractedSamplesCountAtStartOfLoad;
|
||||||
private boolean loadingFinished;
|
private boolean loadingFinished;
|
||||||
|
private boolean released;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param uri The {@link Uri} of the media stream.
|
* @param uri The {@link Uri} of the media stream.
|
||||||
@ -94,30 +98,41 @@ import java.util.Arrays;
|
|||||||
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||||
* @param sourceListener A listener to notify when the timeline has been loaded.
|
* @param sourceListener A listener to notify when the timeline has been loaded.
|
||||||
* @param callback A callback to receive updates from the period.
|
|
||||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||||
*/
|
*/
|
||||||
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
|
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
|
||||||
int minLoadableRetryCount, Handler eventHandler,
|
int minLoadableRetryCount, Handler eventHandler,
|
||||||
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener,
|
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener,
|
||||||
Callback callback, Allocator allocator) {
|
Allocator allocator) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
this.sourceListener = sourceListener;
|
this.sourceListener = sourceListener;
|
||||||
this.callback = callback;
|
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
loader = new Loader("Loader:ExtractorMediaPeriod");
|
loader = new Loader("Loader:ExtractorMediaPeriod");
|
||||||
extractorHolder = new ExtractorHolder(extractors, this);
|
extractorHolder = new ExtractorHolder(extractors, this);
|
||||||
loadCondition = new ConditionVariable();
|
loadCondition = new ConditionVariable();
|
||||||
|
maybeFinishPrepareRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
maybeFinishPrepare();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onContinueLoadingRequestedRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!released) {
|
||||||
|
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler = new Handler();
|
||||||
|
|
||||||
pendingResetPositionUs = C.TIME_UNSET;
|
pendingResetPositionUs = C.TIME_UNSET;
|
||||||
sampleQueues = new DefaultTrackOutput[0];
|
sampleQueues = new DefaultTrackOutput[0];
|
||||||
length = C.LENGTH_UNSET;
|
length = C.LENGTH_UNSET;
|
||||||
loadCondition.open();
|
|
||||||
startLoading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
@ -126,11 +141,20 @@ import java.util.Arrays;
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
extractorHolder.release();
|
extractorHolder.release();
|
||||||
|
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||||
|
sampleQueue.disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
handler.removeCallbacksAndMessages(null);
|
||||||
sampleQueue.disable();
|
released = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
loadCondition.open();
|
||||||
|
startLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -330,7 +354,7 @@ import java.util.Arrays;
|
|||||||
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
|
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractorOutput implementation.
|
// ExtractorOutput implementation. Called by the loading thread.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackOutput track(int id) {
|
public TrackOutput track(int id) {
|
||||||
@ -344,26 +368,26 @@ import java.util.Arrays;
|
|||||||
@Override
|
@Override
|
||||||
public void endTracks() {
|
public void endTracks() {
|
||||||
tracksBuilt = true;
|
tracksBuilt = true;
|
||||||
maybeFinishPrepare();
|
handler.post(maybeFinishPrepareRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekMap(SeekMap seekMap) {
|
public void seekMap(SeekMap seekMap) {
|
||||||
this.seekMap = seekMap;
|
this.seekMap = seekMap;
|
||||||
maybeFinishPrepare();
|
handler.post(maybeFinishPrepareRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamFormatChangedListener implementation
|
// UpstreamFormatChangedListener implementation. Called by the loading thread.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpstreamFormatChanged(Format format) {
|
public void onUpstreamFormatChanged(Format format) {
|
||||||
maybeFinishPrepare();
|
handler.post(maybeFinishPrepareRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void maybeFinishPrepare() {
|
private void maybeFinishPrepare() {
|
||||||
if (prepared || seekMap == null || !tracksBuilt) {
|
if (released || prepared || seekMap == null || !tracksBuilt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||||
@ -576,7 +600,7 @@ import java.util.Arrays;
|
|||||||
if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) {
|
if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) {
|
||||||
position = input.getPosition();
|
position = input.getPosition();
|
||||||
loadCondition.close();
|
loadCondition.close();
|
||||||
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this);
|
handler.post(onContinueLoadingRequestedRunnable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline;
|
|||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(),
|
return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(),
|
||||||
extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener,
|
extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener,
|
||||||
this, callback, allocator);
|
this, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,7 +19,6 @@ import android.util.Log;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
return childSource.createPeriod(index % childPeriodCount, allocator, positionUs);
|
||||||
return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||||||
/**
|
/**
|
||||||
* Called when preparation completes.
|
* Called when preparation completes.
|
||||||
* <p>
|
* <p>
|
||||||
* May be called from any thread. After invoking this method, the {@link MediaPeriod} can expect
|
* Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect
|
||||||
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
|
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
|
||||||
* called with the initial track selection.
|
* called with the initial track selection.
|
||||||
*
|
*
|
||||||
@ -42,6 +42,17 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this media period asynchronously.
|
||||||
|
* <p>
|
||||||
|
* {@code callback.onPrepared} is called when preparation completes. If preparation fails,
|
||||||
|
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
|
||||||
|
*
|
||||||
|
* @param callback Callback to receive updates from this period, including being notified when
|
||||||
|
* preparation completes.
|
||||||
|
*/
|
||||||
|
void prepare(Callback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such
|
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such
|
||||||
* error exists.
|
* error exists.
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -54,18 +53,13 @@ public interface MediaSource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link MediaPeriod} corresponding to the period at the specified index.
|
* Returns a {@link MediaPeriod} corresponding to the period at the specified index.
|
||||||
* <p>
|
|
||||||
* {@link Callback#onPrepared(MediaPeriod)} is called when the new period is prepared. If
|
|
||||||
* preparation fails, {@link MediaPeriod#maybeThrowPrepareError()} will throw an
|
|
||||||
* {@link IOException} if called on the returned instance.
|
|
||||||
*
|
*
|
||||||
* @param index The index of the period.
|
* @param index The index of the period.
|
||||||
* @param callback A callback to receive updates from the period.
|
|
||||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||||
* @param positionUs The player's current playback position.
|
* @param positionUs The player's current playback position.
|
||||||
* @return A new {@link MediaPeriod}.
|
* @return A new {@link MediaPeriod}.
|
||||||
*/
|
*/
|
||||||
MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, long positionUs);
|
MediaPeriod createPeriod(int index, Allocator allocator, long positionUs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the period.
|
* Releases the period.
|
||||||
|
@ -28,20 +28,27 @@ import java.util.IdentityHashMap;
|
|||||||
|
|
||||||
public final MediaPeriod[] periods;
|
public final MediaPeriod[] periods;
|
||||||
|
|
||||||
private final Callback callback;
|
|
||||||
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
|
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private int pendingChildPrepareCount;
|
private int pendingChildPrepareCount;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
|
|
||||||
private MediaPeriod[] enabledPeriods;
|
private MediaPeriod[] enabledPeriods;
|
||||||
private SequenceableLoader sequenceableLoader;
|
private SequenceableLoader sequenceableLoader;
|
||||||
|
|
||||||
public MergingMediaPeriod(Callback callback, MediaPeriod... periods) {
|
public MergingMediaPeriod(MediaPeriod... periods) {
|
||||||
this.periods = periods;
|
this.periods = periods;
|
||||||
this.callback = callback;
|
|
||||||
streamPeriodIndices = new IdentityHashMap<>();
|
streamPeriodIndices = new IdentityHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
pendingChildPrepareCount = periods.length;
|
pendingChildPrepareCount = periods.length;
|
||||||
|
for (MediaPeriod period : periods) {
|
||||||
|
period.prepare(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -36,11 +37,16 @@ public final class MergingMediaSource implements MediaSource {
|
|||||||
*/
|
*/
|
||||||
public static final class IllegalMergeException extends IOException {
|
public static final class IllegalMergeException extends IOException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason the merge failed.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH})
|
||||||
|
public @interface Reason {}
|
||||||
/**
|
/**
|
||||||
* The merge failed because one of the sources being merged has a dynamic window.
|
* The merge failed because one of the sources being merged has a dynamic window.
|
||||||
*/
|
*/
|
||||||
public static final int REASON_WINDOWS_ARE_DYNAMIC = 0;
|
public static final int REASON_WINDOWS_ARE_DYNAMIC = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The merge failed because the sources have different period counts.
|
* The merge failed because the sources have different period counts.
|
||||||
*/
|
*/
|
||||||
@ -50,13 +56,14 @@ public final class MergingMediaSource implements MediaSource {
|
|||||||
* The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
* The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
||||||
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
||||||
*/
|
*/
|
||||||
|
@Reason
|
||||||
public final int reason;
|
public final int reason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
* @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
|
||||||
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
* {@link #REASON_PERIOD_COUNT_MISMATCH}.
|
||||||
*/
|
*/
|
||||||
public IllegalMergeException(int reason) {
|
public IllegalMergeException(@Reason int reason) {
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
|
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
|
||||||
// The periods are only referenced after they have all been prepared.
|
|
||||||
MergingMediaPeriod mergingPeriod = new MergingMediaPeriod(callback, periods);
|
|
||||||
for (int i = 0; i < periods.length; i++) {
|
for (int i = 0; i < periods.length; i++) {
|
||||||
periods[i] = mediaSources[i].createPeriod(index, mergingPeriod, allocator, positionUs);
|
periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs);
|
||||||
Assertions.checkState(periods[i] != null, "Child source must not return null period");
|
|
||||||
}
|
}
|
||||||
return mergingPeriod;
|
return new MergingMediaPeriod(periods);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,7 +29,7 @@ public interface SequenceableLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method
|
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method
|
||||||
* to be called when it can continue to load data.
|
* to be called when it can continue to load data. Called on the playback thread.
|
||||||
*/
|
*/
|
||||||
void onContinueLoadingRequested(T source);
|
void onContinueLoadingRequested(T source);
|
||||||
|
|
||||||
|
@ -78,6 +78,11 @@ import java.util.Arrays;
|
|||||||
loader.release();
|
loader.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
callback.onPrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
loader.maybeThrowError();
|
loader.maybeThrowError();
|
||||||
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
MediaPeriod mediaPeriod = new SingleSampleMediaPeriod(uri, dataSourceFactory, format,
|
return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount,
|
||||||
minLoadableRetryCount, eventHandler, eventListener, eventSourceId);
|
eventHandler, eventListener, eventSourceId);
|
||||||
callback.onPrepared(mediaPeriod);
|
|
||||||
return mediaPeriod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.chunk;
|
package com.google.android.exoplayer2.source.chunk;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||||
|
byte[] encryptionKey) {
|
||||||
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
|
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
|
||||||
|
byte[] encryptionKey) {
|
||||||
throw new IllegalStateException("Unexpected sample data in initialization chunk");
|
throw new IllegalStateException("Unexpected sample data in initialization chunk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,10 @@ import java.util.List;
|
|||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final long elapsedRealtimeOffset;
|
private final long elapsedRealtimeOffset;
|
||||||
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
||||||
private final Callback callback;
|
|
||||||
private final Allocator allocator;
|
private final Allocator allocator;
|
||||||
private final TrackGroupArray trackGroups;
|
private final TrackGroupArray trackGroups;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
private CompositeSequenceableLoader sequenceableLoader;
|
||||||
private DashManifest manifest;
|
private DashManifest manifest;
|
||||||
@ -61,7 +61,7 @@ import java.util.List;
|
|||||||
public DashMediaPeriod(int id, DashManifest manifest, int index,
|
public DashMediaPeriod(int id, DashManifest manifest, int index,
|
||||||
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
|
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
|
||||||
EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
|
EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
|
||||||
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) {
|
LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@ -70,13 +70,11 @@ import java.util.List;
|
|||||||
this.eventDispatcher = eventDispatcher;
|
this.eventDispatcher = eventDispatcher;
|
||||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
||||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||||
this.callback = callback;
|
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
sampleStreams = newSampleStreamArray(0);
|
sampleStreams = newSampleStreamArray(0);
|
||||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||||
period = manifest.getPeriod(index);
|
period = manifest.getPeriod(index);
|
||||||
trackGroups = buildTrackGroups(period);
|
trackGroups = buildTrackGroups(period);
|
||||||
callback.onPrepared(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateManifest(DashManifest manifest, int index) {
|
public void updateManifest(DashManifest manifest, int index) {
|
||||||
@ -97,6 +95,12 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
callback.onPrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
manifestLoaderErrorThrower.maybeThrowError();
|
manifestLoaderErrorThrower.maybeThrowError();
|
||||||
|
@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline;
|
|||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||||
@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index,
|
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index,
|
||||||
chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader,
|
chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader,
|
||||||
callback, allocator);
|
allocator);
|
||||||
periodsById.put(mediaPeriod.id, mediaPeriod);
|
periodsById.put(mediaPeriod.id, mediaPeriod);
|
||||||
return mediaPeriod;
|
return mediaPeriod;
|
||||||
}
|
}
|
||||||
|
@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
} else if (MimeTypes.isVideo(containerMimeType)) {
|
} else if (MimeTypes.isVideo(containerMimeType)) {
|
||||||
return MimeTypes.getVideoMediaMimeType(codecs);
|
return MimeTypes.getVideoMediaMimeType(codecs);
|
||||||
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
|
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
|
||||||
// We currently only support EIA-608 through RawCC
|
// We currently only support CEA-608 through RawCC
|
||||||
if (codecs != null && codecs.contains("eia608")) {
|
if (codecs != null
|
||||||
return MimeTypes.APPLICATION_EIA608;
|
&& (codecs.contains("eia608") || codecs.contains("cea608"))) {
|
||||||
|
return MimeTypes.APPLICATION_CEA608;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} else if (mimeTypeIsRawText(containerMimeType)) {
|
} else if (mimeTypeIsRawText(containerMimeType)) {
|
||||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor;
|
|||||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
|
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||||
@ -355,20 +356,22 @@ import java.util.Locale;
|
|||||||
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
|
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
|
||||||
startTimeUs);
|
startTimeUs);
|
||||||
// This flag ensures the change of pid between streams does not affect the sample queues.
|
// This flag ensures the change of pid between streams does not affect the sample queues.
|
||||||
int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE;
|
@DefaultStreamReaderFactory.WorkaroundFlags
|
||||||
|
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
|
||||||
String codecs = variants[newVariantIndex].format.codecs;
|
String codecs = variants[newVariantIndex].format.codecs;
|
||||||
if (!TextUtils.isEmpty(codecs)) {
|
if (!TextUtils.isEmpty(codecs)) {
|
||||||
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
|
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
|
||||||
// exist. If we know from the codec attribute that they don't exist, then we can explicitly
|
// exist. If we know from the codec attribute that they don't exist, then we can explicitly
|
||||||
// ignore them even if they're declared.
|
// ignore them even if they're declared.
|
||||||
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
|
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
|
||||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM;
|
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_AAC_STREAM;
|
||||||
}
|
}
|
||||||
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
|
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
|
||||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
|
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
|
extractor = new TsExtractor(timestampAdjuster,
|
||||||
|
new DefaultStreamReaderFactory(workaroundFlags));
|
||||||
} else {
|
} else {
|
||||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||||
extractor = previous.extractor;
|
extractor = previous.extractor;
|
||||||
|
@ -50,11 +50,11 @@ import java.util.List;
|
|||||||
/* package */ final class HlsMediaPeriod implements MediaPeriod,
|
/* package */ final class HlsMediaPeriod implements MediaPeriod,
|
||||||
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback {
|
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback {
|
||||||
|
|
||||||
|
private final Uri manifestUri;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final MediaSource.Listener sourceListener;
|
private final MediaSource.Listener sourceListener;
|
||||||
private final Callback callback;
|
|
||||||
private final Allocator allocator;
|
private final Allocator allocator;
|
||||||
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
|
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
|
||||||
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
@ -62,7 +62,9 @@ import java.util.List;
|
|||||||
private final Handler continueLoadingHandler;
|
private final Handler continueLoadingHandler;
|
||||||
private final Loader manifestFetcher;
|
private final Loader manifestFetcher;
|
||||||
private final long preparePositionUs;
|
private final long preparePositionUs;
|
||||||
|
private final Runnable continueLoadingRunnable;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private int pendingPrepareCount;
|
private int pendingPrepareCount;
|
||||||
private HlsPlaylist playlist;
|
private HlsPlaylist playlist;
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
@ -72,17 +74,16 @@ import java.util.List;
|
|||||||
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
||||||
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
private CompositeSequenceableLoader sequenceableLoader;
|
||||||
private Runnable continueLoadingRunnable;
|
|
||||||
|
|
||||||
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
|
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
|
||||||
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
||||||
MediaSource.Listener sourceListener, final Callback callback, Allocator allocator,
|
MediaSource.Listener sourceListener, Allocator allocator,
|
||||||
long positionUs) {
|
long positionUs) {
|
||||||
|
this.manifestUri = manifestUri;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
this.eventDispatcher = eventDispatcher;
|
this.eventDispatcher = eventDispatcher;
|
||||||
this.sourceListener = sourceListener;
|
this.sourceListener = sourceListener;
|
||||||
this.callback = callback;
|
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
streamWrapperIndices = new IdentityHashMap<>();
|
streamWrapperIndices = new IdentityHashMap<>();
|
||||||
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
||||||
@ -96,11 +97,6 @@ import java.util.List;
|
|||||||
callback.onContinueLoadingRequested(HlsMediaPeriod.this);
|
callback.onContinueLoadingRequested(HlsMediaPeriod.this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
|
|
||||||
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
|
||||||
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
|
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
@ -111,6 +107,15 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
|
||||||
|
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||||
|
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
|
||||||
|
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
if (sampleStreamWrappers == null) {
|
if (sampleStreamWrappers == null) {
|
||||||
@ -239,13 +244,7 @@ import java.util.List;
|
|||||||
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
|
||||||
loadDurationMs, loadable.bytesLoaded());
|
loadDurationMs, loadable.bytesLoaded());
|
||||||
playlist = loadable.getResult();
|
playlist = loadable.getResult();
|
||||||
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers();
|
buildAndPrepareSampleStreamWrappers();
|
||||||
sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()];
|
|
||||||
sampleStreamWrapperList.toArray(sampleStreamWrappers);
|
|
||||||
pendingPrepareCount = sampleStreamWrappers.length;
|
|
||||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
|
||||||
sampleStreamWrapper.prepare();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -313,16 +312,16 @@ import java.util.List;
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers() {
|
private void buildAndPrepareSampleStreamWrappers() {
|
||||||
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
|
|
||||||
String baseUri = playlist.baseUri;
|
String baseUri = playlist.baseUri;
|
||||||
|
|
||||||
if (playlist instanceof HlsMediaPlaylist) {
|
if (playlist instanceof HlsMediaPlaylist) {
|
||||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
|
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
|
||||||
HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
|
HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
|
||||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
sampleStreamWrappers = new HlsSampleStreamWrapper[] {
|
||||||
null, null));
|
buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)};
|
||||||
return sampleStreamWrappers;
|
pendingPrepareCount = 1;
|
||||||
|
sampleStreamWrappers[0].prepare();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
|
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
|
||||||
@ -351,32 +350,37 @@ import java.util.List;
|
|||||||
} else {
|
} else {
|
||||||
// Leave the enabled variants unchanged. They're likely either all video or all audio.
|
// Leave the enabled variants unchanged. They're likely either all video or all audio.
|
||||||
}
|
}
|
||||||
|
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios;
|
||||||
|
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles;
|
||||||
|
sampleStreamWrappers = new HlsSampleStreamWrapper[(selectedVariants.isEmpty() ? 0 : 1)
|
||||||
|
+ audioVariants.size() + subtitleVariants.size()];
|
||||||
|
int currentWrapperIndex = 0;
|
||||||
|
pendingPrepareCount = sampleStreamWrappers.length;
|
||||||
if (!selectedVariants.isEmpty()) {
|
if (!selectedVariants.isEmpty()) {
|
||||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()];
|
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()];
|
||||||
selectedVariants.toArray(variants);
|
selectedVariants.toArray(variants);
|
||||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT,
|
||||||
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat));
|
baseUri, variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat);
|
||||||
|
sampleStreamWrapper.prepare();
|
||||||
|
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the audio stream wrapper if applicable.
|
// Build audio stream wrappers.
|
||||||
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios;
|
for (int i = 0; i < audioVariants.size(); i++) {
|
||||||
if (!audioVariants.isEmpty()) {
|
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO,
|
||||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[audioVariants.size()];
|
baseUri, new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null);
|
||||||
audioVariants.toArray(variants);
|
sampleStreamWrapper.prepare();
|
||||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
|
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||||
null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the text stream wrapper if applicable.
|
// Build subtitle stream wrappers.
|
||||||
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles;
|
for (int i = 0; i < subtitleVariants.size(); i++) {
|
||||||
if (!subtitleVariants.isEmpty()) {
|
HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i);
|
||||||
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[subtitleVariants.size()];
|
HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT,
|
||||||
subtitleVariants.toArray(variants);
|
baseUri, new HlsMasterPlaylist.HlsUrl[] {url}, null, null);
|
||||||
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
|
sampleStreamWrapper.prepareSingleTrack(url.format);
|
||||||
null));
|
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sampleStreamWrappers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
|
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
|
||||||
|
@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount,
|
return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount,
|
||||||
eventDispatcher, sourceListener, callback, allocator, positionUs);
|
eventDispatcher, sourceListener, allocator, positionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.hls;
|
package com.google.android.exoplayer2.source.hls;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
@ -80,13 +82,15 @@ import java.util.LinkedList;
|
|||||||
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
|
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
|
||||||
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
||||||
private final LinkedList<HlsMediaChunk> mediaChunks;
|
private final LinkedList<HlsMediaChunk> mediaChunks;
|
||||||
|
private final Runnable maybeFinishPrepareRunnable;
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
private volatile boolean sampleQueuesBuilt;
|
private boolean sampleQueuesBuilt;
|
||||||
|
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private int enabledTrackCount;
|
private int enabledTrackCount;
|
||||||
private Format downstreamTrackFormat;
|
private Format downstreamTrackFormat;
|
||||||
private int upstreamChunkUid;
|
private int upstreamChunkUid;
|
||||||
|
private boolean released;
|
||||||
|
|
||||||
// Tracks are complicated in HLS. See documentation of buildTracks for details.
|
// Tracks are complicated in HLS. See documentation of buildTracks for details.
|
||||||
// Indexed by track (as exposed by this source).
|
// Indexed by track (as exposed by this source).
|
||||||
@ -129,6 +133,13 @@ import java.util.LinkedList;
|
|||||||
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
|
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
|
||||||
sampleQueues = new SparseArray<>();
|
sampleQueues = new SparseArray<>();
|
||||||
mediaChunks = new LinkedList<>();
|
mediaChunks = new LinkedList<>();
|
||||||
|
maybeFinishPrepareRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
maybeFinishPrepare();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler = new Handler();
|
||||||
lastSeekPositionUs = positionUs;
|
lastSeekPositionUs = positionUs;
|
||||||
pendingResetPositionUs = positionUs;
|
pendingResetPositionUs = positionUs;
|
||||||
}
|
}
|
||||||
@ -137,6 +148,15 @@ import java.util.LinkedList;
|
|||||||
continueLoading(lastSeekPositionUs);
|
continueLoading(lastSeekPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a sample stream wrapper for which the master playlist provides enough information to
|
||||||
|
* prepare.
|
||||||
|
*/
|
||||||
|
public void prepareSingleTrack(Format format) {
|
||||||
|
track(0).format(format);
|
||||||
|
endTracks();
|
||||||
|
}
|
||||||
|
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
maybeThrowError();
|
maybeThrowError();
|
||||||
}
|
}
|
||||||
@ -245,6 +265,8 @@ import java.util.LinkedList;
|
|||||||
sampleQueues.valueAt(i).disable();
|
sampleQueues.valueAt(i).disable();
|
||||||
}
|
}
|
||||||
loader.release();
|
loader.release();
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
released = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLargestQueuedTimestampUs() {
|
public long getLargestQueuedTimestampUs() {
|
||||||
@ -454,7 +476,7 @@ import java.util.LinkedList;
|
|||||||
@Override
|
@Override
|
||||||
public void endTracks() {
|
public void endTracks() {
|
||||||
sampleQueuesBuilt = true;
|
sampleQueuesBuilt = true;
|
||||||
maybeFinishPrepare();
|
handler.post(maybeFinishPrepareRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -462,17 +484,17 @@ import java.util.LinkedList;
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamFormatChangedListener implementation.
|
// UpstreamFormatChangedListener implementation. Called by the loading thread.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpstreamFormatChanged(Format format) {
|
public void onUpstreamFormatChanged(Format format) {
|
||||||
maybeFinishPrepare();
|
handler.post(maybeFinishPrepareRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void maybeFinishPrepare() {
|
private void maybeFinishPrepare() {
|
||||||
if (prepared || !sampleQueuesBuilt) {
|
if (released || prepared || !sampleQueuesBuilt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int sampleQueueCount = sampleQueues.size();
|
int sampleQueueCount = sampleQueues.size();
|
||||||
@ -558,7 +580,7 @@ import java.util.LinkedList;
|
|||||||
if (i == primaryExtractorTrackIndex) {
|
if (i == primaryExtractorTrackIndex) {
|
||||||
Format[] formats = new Format[chunkSourceTrackCount];
|
Format[] formats = new Format[chunkSourceTrackCount];
|
||||||
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
||||||
formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
|
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
|
||||||
}
|
}
|
||||||
trackGroups[i] = new TrackGroup(formats);
|
trackGroups[i] = new TrackGroup(formats);
|
||||||
primaryTrackGroupIndex = i;
|
primaryTrackGroupIndex = i;
|
||||||
@ -567,11 +589,11 @@ import java.util.LinkedList;
|
|||||||
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
|
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
|
||||||
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
|
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
|
||||||
trackFormat = muxedAudioFormat;
|
trackFormat = muxedAudioFormat;
|
||||||
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
|
} else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) {
|
||||||
trackFormat = muxedCaptionFormat;
|
trackFormat = muxedCaptionFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
|
trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.trackGroups = new TrackGroupArray(trackGroups);
|
this.trackGroups = new TrackGroupArray(trackGroups);
|
||||||
@ -590,18 +612,25 @@ import java.util.LinkedList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a sample format corresponding to a given container format, by combining it with sample
|
* Derives a track format corresponding to a given container format, by combining it with sample
|
||||||
* level information obtained from a second sample format.
|
* level information obtained from the samples.
|
||||||
*
|
*
|
||||||
* @param containerFormat The container format for which the sample format should be derived.
|
* @param containerFormat The container format for which the track format should be derived.
|
||||||
* @param sampleFormat A sample format from which to obtain sample level information.
|
* @param sampleFormat A sample format from which to obtain sample level information.
|
||||||
* @return The derived sample format.
|
* @return The derived track format.
|
||||||
*/
|
*/
|
||||||
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
|
private static Format deriveFormat(Format containerFormat, Format sampleFormat) {
|
||||||
if (containerFormat == null) {
|
if (containerFormat == null) {
|
||||||
return sampleFormat;
|
return sampleFormat;
|
||||||
}
|
}
|
||||||
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate,
|
String codecs = null;
|
||||||
|
int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType);
|
||||||
|
if (sampleTrackType == C.TRACK_TYPE_AUDIO) {
|
||||||
|
codecs = getAudioCodecs(containerFormat.codecs);
|
||||||
|
} else if (sampleTrackType == C.TRACK_TYPE_VIDEO) {
|
||||||
|
codecs = getVideoCodecs(containerFormat.codecs);
|
||||||
|
}
|
||||||
|
return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate,
|
||||||
containerFormat.width, containerFormat.height, containerFormat.selectionFlags,
|
containerFormat.width, containerFormat.height, containerFormat.selectionFlags,
|
||||||
containerFormat.language);
|
containerFormat.language);
|
||||||
}
|
}
|
||||||
@ -614,4 +643,29 @@ import java.util.LinkedList;
|
|||||||
return pendingResetPositionUs != C.TIME_UNSET;
|
return pendingResetPositionUs != C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getAudioCodecs(String codecs) {
|
||||||
|
return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getVideoCodecs(String codecs) {
|
||||||
|
return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCodecsOfType(String codecs, int trackType) {
|
||||||
|
if (TextUtils.isEmpty(codecs)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (String codec : codecArray) {
|
||||||
|
if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) {
|
||||||
|
if (builder.length() > 0) {
|
||||||
|
builder.append(",");
|
||||||
|
}
|
||||||
|
builder.append(codec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.length() > 0 ? builder.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,29 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an HLS playlist.
|
* Represents an HLS playlist.
|
||||||
*/
|
*/
|
||||||
public abstract class HlsPlaylist {
|
public abstract class HlsPlaylist {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of playlist.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TYPE_MASTER, TYPE_MEDIA})
|
||||||
|
public @interface Type {}
|
||||||
public static final int TYPE_MASTER = 0;
|
public static final int TYPE_MASTER = 0;
|
||||||
public static final int TYPE_MEDIA = 1;
|
public static final int TYPE_MEDIA = 1;
|
||||||
|
|
||||||
public final String baseUri;
|
public final String baseUri;
|
||||||
|
@Type
|
||||||
public final int type;
|
public final int type;
|
||||||
|
|
||||||
protected HlsPlaylist(String baseUri, int type) {
|
protected HlsPlaylist(String baseUri, @Type int type) {
|
||||||
this.baseUri = baseUri;
|
this.baseUri = baseUri;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
private static final String BOOLEAN_TRUE = "YES";
|
private static final String BOOLEAN_TRUE = "YES";
|
||||||
private static final String BOOLEAN_FALSE = "NO";
|
private static final String BOOLEAN_FALSE = "NO";
|
||||||
|
|
||||||
|
|
||||||
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
|
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
|
||||||
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
|
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
|
||||||
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
|
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
|
||||||
@ -138,7 +137,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
line = iterator.next();
|
line = iterator.next();
|
||||||
if (line.startsWith(TAG_MEDIA)) {
|
if (line.startsWith(TAG_MEDIA)) {
|
||||||
int selectionFlags = parseSelectionFlags(line);
|
@C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
|
||||||
String uri = parseOptionalStringAttr(line, REGEX_URI);
|
String uri = parseOptionalStringAttr(line, REGEX_URI);
|
||||||
String name = parseStringAttr(line, REGEX_NAME);
|
String name = parseStringAttr(line, REGEX_NAME);
|
||||||
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
|
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
|
||||||
@ -162,7 +161,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
case TYPE_CLOSED_CAPTIONS:
|
case TYPE_CLOSED_CAPTIONS:
|
||||||
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
|
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
|
||||||
muxedCaptionFormat = Format.createTextContainerFormat(name,
|
muxedCaptionFormat = Format.createTextContainerFormat(name,
|
||||||
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE,
|
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE,
|
||||||
selectionFlags, language);
|
selectionFlags, language);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -200,11 +199,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
muxedCaptionFormat);
|
muxedCaptionFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@C.SelectionFlags
|
||||||
private static int parseSelectionFlags(String line) {
|
private static int parseSelectionFlags(String line) {
|
||||||
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0)
|
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0)
|
||||||
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0)
|
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0)
|
||||||
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT
|
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0);
|
||||||
: 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
||||||
|
@ -46,23 +46,22 @@ import java.util.ArrayList;
|
|||||||
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final Callback callback;
|
|
||||||
private final Allocator allocator;
|
private final Allocator allocator;
|
||||||
private final TrackGroupArray trackGroups;
|
private final TrackGroupArray trackGroups;
|
||||||
private final TrackEncryptionBox[] trackEncryptionBoxes;
|
private final TrackEncryptionBox[] trackEncryptionBoxes;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private SsManifest manifest;
|
private SsManifest manifest;
|
||||||
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
private CompositeSequenceableLoader sequenceableLoader;
|
||||||
|
|
||||||
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
|
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
|
||||||
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
int minLoadableRetryCount, EventDispatcher eventDispatcher,
|
||||||
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) {
|
LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
|
||||||
this.chunkSourceFactory = chunkSourceFactory;
|
this.chunkSourceFactory = chunkSourceFactory;
|
||||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
this.eventDispatcher = eventDispatcher;
|
this.eventDispatcher = eventDispatcher;
|
||||||
this.callback = callback;
|
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
|
|
||||||
trackGroups = buildTrackGroups(manifest);
|
trackGroups = buildTrackGroups(manifest);
|
||||||
@ -77,7 +76,6 @@ import java.util.ArrayList;
|
|||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
sampleStreams = newSampleStreamArray(0);
|
sampleStreams = newSampleStreamArray(0);
|
||||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||||
callback.onPrepared(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateManifest(SsManifest manifest) {
|
public void updateManifest(SsManifest manifest) {
|
||||||
@ -94,6 +92,12 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
callback.onPrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
manifestLoaderErrorThrower.maybeThrowError();
|
manifestLoaderErrorThrower.maybeThrowError();
|
||||||
|
@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline;
|
|||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
||||||
@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
long positionUs) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
|
SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
|
||||||
eventDispatcher, manifestLoader, callback, allocator);
|
eventDispatcher, manifestLoader, allocator);
|
||||||
mediaPeriods.add(period);
|
mediaPeriods.add(period);
|
||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
@ -18,35 +18,41 @@ package com.google.android.exoplayer2.text;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.view.accessibility.CaptioningManager;
|
import android.view.accessibility.CaptioningManager;
|
||||||
import android.view.accessibility.CaptioningManager.CaptionStyle;
|
import android.view.accessibility.CaptioningManager.CaptionStyle;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A compatibility wrapper for {@link CaptionStyle}.
|
* A compatibility wrapper for {@link CaptionStyle}.
|
||||||
*/
|
*/
|
||||||
public final class CaptionStyleCompat {
|
public final class CaptionStyleCompat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of edge, which may be none.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED,
|
||||||
|
EDGE_TYPE_DEPRESSED})
|
||||||
|
public @interface EdgeType {}
|
||||||
/**
|
/**
|
||||||
* Edge type value specifying no character edges.
|
* Edge type value specifying no character edges.
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_TYPE_NONE = 0;
|
public static final int EDGE_TYPE_NONE = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edge type value specifying uniformly outlined character edges.
|
* Edge type value specifying uniformly outlined character edges.
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_TYPE_OUTLINE = 1;
|
public static final int EDGE_TYPE_OUTLINE = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edge type value specifying drop-shadowed character edges.
|
* Edge type value specifying drop-shadowed character edges.
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_TYPE_DROP_SHADOW = 2;
|
public static final int EDGE_TYPE_DROP_SHADOW = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edge type value specifying raised bevel character edges.
|
* Edge type value specifying raised bevel character edges.
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_TYPE_RAISED = 3;
|
public static final int EDGE_TYPE_RAISED = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edge type value specifying depressed bevel character edges.
|
* Edge type value specifying depressed bevel character edges.
|
||||||
*/
|
*/
|
||||||
@ -88,6 +94,7 @@ public final class CaptionStyleCompat {
|
|||||||
* <li>{@link #EDGE_TYPE_DEPRESSED}
|
* <li>{@link #EDGE_TYPE_DEPRESSED}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
@EdgeType
|
||||||
public final int edgeType;
|
public final int edgeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,8 +133,8 @@ public final class CaptionStyleCompat {
|
|||||||
* @param edgeColor See {@link #edgeColor}.
|
* @param edgeColor See {@link #edgeColor}.
|
||||||
* @param typeface See {@link #typeface}.
|
* @param typeface See {@link #typeface}.
|
||||||
*/
|
*/
|
||||||
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType,
|
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor,
|
||||||
int edgeColor, Typeface typeface) {
|
@EdgeType int edgeType, int edgeColor, Typeface typeface) {
|
||||||
this.foregroundColor = foregroundColor;
|
this.foregroundColor = foregroundColor;
|
||||||
this.backgroundColor = backgroundColor;
|
this.backgroundColor = backgroundColor;
|
||||||
this.windowColor = windowColor;
|
this.windowColor = windowColor;
|
||||||
@ -137,6 +144,7 @@ public final class CaptionStyleCompat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(19)
|
@TargetApi(19)
|
||||||
|
@SuppressWarnings("ResourceType")
|
||||||
private static CaptionStyleCompat createFromCaptionStyleV19(
|
private static CaptionStyleCompat createFromCaptionStyleV19(
|
||||||
CaptioningManager.CaptionStyle captionStyle) {
|
CaptioningManager.CaptionStyle captionStyle) {
|
||||||
return new CaptionStyleCompat(
|
return new CaptionStyleCompat(
|
||||||
@ -145,6 +153,7 @@ public final class CaptionStyleCompat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
|
@SuppressWarnings("ResourceType")
|
||||||
private static CaptionStyleCompat createFromCaptionStyleV21(
|
private static CaptionStyleCompat createFromCaptionStyleV21(
|
||||||
CaptioningManager.CaptionStyle captionStyle) {
|
CaptioningManager.CaptionStyle captionStyle) {
|
||||||
return new CaptionStyleCompat(
|
return new CaptionStyleCompat(
|
||||||
|
@ -15,7 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text;
|
package com.google.android.exoplayer2.text;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains information about a specific cue, including textual content and formatting data.
|
* Contains information about a specific cue, including textual content and formatting data.
|
||||||
@ -26,6 +29,13 @@ public class Cue {
|
|||||||
* An unset position or width.
|
* An unset position or width.
|
||||||
*/
|
*/
|
||||||
public static final float DIMEN_UNSET = Float.MIN_VALUE;
|
public static final float DIMEN_UNSET = Float.MIN_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of anchor, which may be unset.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
|
||||||
|
public @interface AnchorType {}
|
||||||
/**
|
/**
|
||||||
* An unset anchor or line type value.
|
* An unset anchor or line type value.
|
||||||
*/
|
*/
|
||||||
@ -44,6 +54,13 @@ public class Cue {
|
|||||||
* box.
|
* box.
|
||||||
*/
|
*/
|
||||||
public static final int ANCHOR_TYPE_END = 2;
|
public static final int ANCHOR_TYPE_END = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of line, which may be unset.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
|
||||||
|
public @interface LineType {}
|
||||||
/**
|
/**
|
||||||
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
||||||
*/
|
*/
|
||||||
@ -83,6 +100,7 @@ public class Cue {
|
|||||||
* -1). For horizontal text the size of the first line of the cue is its height, and the start
|
* -1). For horizontal text the size of the first line of the cue is its height, and the start
|
||||||
* and end of the viewport are the top and bottom respectively.
|
* and end of the viewport are the top and bottom respectively.
|
||||||
*/
|
*/
|
||||||
|
@LineType
|
||||||
public final int lineType;
|
public final int lineType;
|
||||||
/**
|
/**
|
||||||
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
|
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
|
||||||
@ -92,6 +110,7 @@ public class Cue {
|
|||||||
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
||||||
* respectively.
|
* respectively.
|
||||||
*/
|
*/
|
||||||
|
@AnchorType
|
||||||
public final int lineAnchor;
|
public final int lineAnchor;
|
||||||
/**
|
/**
|
||||||
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
|
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
|
||||||
@ -110,6 +129,7 @@ public class Cue {
|
|||||||
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
|
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
|
||||||
* respectively.
|
* respectively.
|
||||||
*/
|
*/
|
||||||
|
@AnchorType
|
||||||
public final int positionAnchor;
|
public final int positionAnchor;
|
||||||
/**
|
/**
|
||||||
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
||||||
@ -137,8 +157,8 @@ public class Cue {
|
|||||||
* @param positionAnchor See {@link #positionAnchor}.
|
* @param positionAnchor See {@link #positionAnchor}.
|
||||||
* @param size See {@link #size}.
|
* @param size See {@link #size}.
|
||||||
*/
|
*/
|
||||||
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor,
|
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
|
||||||
float position, int positionAnchor, float size) {
|
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.textAlignment = textAlignment;
|
this.textAlignment = textAlignment;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.text;
|
package com.google.android.exoplayer2.text;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.text.eia608.Eia608Decoder;
|
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
|
||||||
import com.google.android.exoplayer2.text.subrip.SubripDecoder;
|
import com.google.android.exoplayer2.text.subrip.SubripDecoder;
|
||||||
import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
|
import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
|
||||||
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
|
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
|
||||||
@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory {
|
|||||||
* <li>TTML ({@link TtmlDecoder})</li>
|
* <li>TTML ({@link TtmlDecoder})</li>
|
||||||
* <li>SubRip ({@link SubripDecoder})</li>
|
* <li>SubRip ({@link SubripDecoder})</li>
|
||||||
* <li>TX3G ({@link Tx3gDecoder})</li>
|
* <li>TX3G ({@link Tx3gDecoder})</li>
|
||||||
* <li>Eia608 ({@link Eia608Decoder})</li>
|
* <li>Cea608 ({@link Cea608Decoder})</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
|
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
|
||||||
@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory {
|
|||||||
return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder");
|
return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder");
|
||||||
case MimeTypes.APPLICATION_TX3G:
|
case MimeTypes.APPLICATION_TX3G:
|
||||||
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
|
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
|
||||||
case MimeTypes.APPLICATION_EIA608:
|
case MimeTypes.APPLICATION_CEA608:
|
||||||
return Class.forName("com.google.android.exoplayer2.text.eia608.Eia608Decoder");
|
return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -13,26 +13,22 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.eia608;
|
package com.google.android.exoplayer2.text.cea;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoder;
|
import com.google.android.exoplayer2.text.SubtitleDecoder;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
|
||||||
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
||||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SubtitleDecoder} for EIA-608 (also known as "line 21 captions" and "CEA-608").
|
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
|
||||||
*/
|
*/
|
||||||
public final class Eia608Decoder implements SubtitleDecoder {
|
public final class Cea608Decoder extends CeaDecoder {
|
||||||
|
|
||||||
private static final int NUM_INPUT_BUFFERS = 10;
|
private static final int NTSC_CC_FIELD_1 = 0x00;
|
||||||
private static final int NUM_OUTPUT_BUFFERS = 2;
|
private static final int CC_VALID_FLAG = 0x04;
|
||||||
|
|
||||||
private static final int PAYLOAD_TYPE_CC = 4;
|
private static final int PAYLOAD_TYPE_CC = 4;
|
||||||
private static final int COUNTRY_CODE = 0xB5;
|
private static final int COUNTRY_CODE = 0xB5;
|
||||||
@ -159,18 +155,10 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
|
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
|
||||||
};
|
};
|
||||||
|
|
||||||
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
|
|
||||||
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
|
||||||
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
|
||||||
|
|
||||||
private final ParsableByteArray ccData;
|
private final ParsableByteArray ccData;
|
||||||
|
|
||||||
private final StringBuilder captionStringBuilder;
|
private final StringBuilder captionStringBuilder;
|
||||||
|
|
||||||
private long playbackPositionUs;
|
|
||||||
|
|
||||||
private SubtitleInputBuffer dequeuedInputBuffer;
|
|
||||||
|
|
||||||
private int captionMode;
|
private int captionMode;
|
||||||
private int captionRowCount;
|
private int captionRowCount;
|
||||||
private String captionString;
|
private String captionString;
|
||||||
@ -181,17 +169,7 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
private byte repeatableControlCc1;
|
private byte repeatableControlCc1;
|
||||||
private byte repeatableControlCc2;
|
private byte repeatableControlCc2;
|
||||||
|
|
||||||
public Eia608Decoder() {
|
public Cea608Decoder() {
|
||||||
availableInputBuffers = new LinkedList<>();
|
|
||||||
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
|
||||||
availableInputBuffers.add(new SubtitleInputBuffer());
|
|
||||||
}
|
|
||||||
availableOutputBuffers = new LinkedList<>();
|
|
||||||
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
|
||||||
availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this));
|
|
||||||
}
|
|
||||||
queuedInputBuffers = new TreeSet<>();
|
|
||||||
|
|
||||||
ccData = new ParsableByteArray();
|
ccData = new ParsableByteArray();
|
||||||
|
|
||||||
captionStringBuilder = new StringBuilder();
|
captionStringBuilder = new StringBuilder();
|
||||||
@ -202,101 +180,20 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Eia608Decoder";
|
return "Cea608Decoder";
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPositionUs(long positionUs) {
|
|
||||||
playbackPositionUs = positionUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
|
|
||||||
Assertions.checkState(dequeuedInputBuffer == null);
|
|
||||||
if (availableInputBuffers.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
dequeuedInputBuffer = availableInputBuffers.pollFirst();
|
|
||||||
return dequeuedInputBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
|
|
||||||
Assertions.checkArgument(inputBuffer != null);
|
|
||||||
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
|
|
||||||
queuedInputBuffers.add(inputBuffer);
|
|
||||||
dequeuedInputBuffer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
|
|
||||||
if (availableOutputBuffers.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate through all available input buffers whose timestamps are less than or equal
|
|
||||||
// to the current playback position; processing input buffers for future content should
|
|
||||||
// be deferred until they would be applicable
|
|
||||||
while (!queuedInputBuffers.isEmpty()
|
|
||||||
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
|
|
||||||
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
|
|
||||||
|
|
||||||
// If the input buffer indicates we've reached the end of the stream, we can
|
|
||||||
// return immediately with an output buffer propagating that
|
|
||||||
if (inputBuffer.isEndOfStream()) {
|
|
||||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
|
||||||
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
|
||||||
releaseInputBuffer(inputBuffer);
|
|
||||||
return outputBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
decode(inputBuffer);
|
|
||||||
|
|
||||||
// check if we have any caption updates to report
|
|
||||||
if (!TextUtils.equals(captionString, lastCaptionString)) {
|
|
||||||
lastCaptionString = captionString;
|
|
||||||
if (!inputBuffer.isDecodeOnly()) {
|
|
||||||
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
|
||||||
outputBuffer.setContent(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0);
|
|
||||||
releaseInputBuffer(inputBuffer);
|
|
||||||
return outputBuffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseInputBuffer(inputBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
|
|
||||||
inputBuffer.clear();
|
|
||||||
availableInputBuffers.add(inputBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
|
|
||||||
outputBuffer.clear();
|
|
||||||
availableOutputBuffers.add(outputBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush() {
|
||||||
|
super.flush();
|
||||||
setCaptionMode(CC_MODE_UNKNOWN);
|
setCaptionMode(CC_MODE_UNKNOWN);
|
||||||
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||||
playbackPositionUs = 0;
|
|
||||||
captionStringBuilder.setLength(0);
|
captionStringBuilder.setLength(0);
|
||||||
captionString = null;
|
captionString = null;
|
||||||
lastCaptionString = null;
|
lastCaptionString = null;
|
||||||
repeatableControlSet = false;
|
repeatableControlSet = false;
|
||||||
repeatableControlCc1 = 0;
|
repeatableControlCc1 = 0;
|
||||||
repeatableControlCc2 = 0;
|
repeatableControlCc2 = 0;
|
||||||
while (!queuedInputBuffers.isEmpty()) {
|
|
||||||
releaseInputBuffer(queuedInputBuffers.pollFirst());
|
|
||||||
}
|
|
||||||
if (dequeuedInputBuffer != null) {
|
|
||||||
releaseInputBuffer(dequeuedInputBuffer);
|
|
||||||
dequeuedInputBuffer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -304,14 +201,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decode(SubtitleInputBuffer inputBuffer) {
|
@Override
|
||||||
|
protected boolean isNewSubtitleDataAvailable() {
|
||||||
|
return !TextUtils.equals(captionString, lastCaptionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Subtitle createSubtitle() {
|
||||||
|
lastCaptionString = captionString;
|
||||||
|
return new CeaSubtitle(new Cue(captionString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(SubtitleInputBuffer inputBuffer) {
|
||||||
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
|
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
|
||||||
boolean captionDataProcessed = false;
|
boolean captionDataProcessed = false;
|
||||||
boolean isRepeatableControl = false;
|
boolean isRepeatableControl = false;
|
||||||
while (ccData.bytesLeft() > 0) {
|
while (ccData.bytesLeft() > 0) {
|
||||||
|
byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07);
|
||||||
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||||
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||||
|
|
||||||
|
// Only examine valid NTSC_CC_FIELD_1 packets
|
||||||
|
if (ccTypeAndValid != (CC_VALID_FLAG | NTSC_CC_FIELD_1)) {
|
||||||
|
// TODO: Add support for NTSC_CC_FIELD_2 packets
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore empty captions.
|
// Ignore empty captions.
|
||||||
if (ccData1 == 0 && ccData2 == 0) {
|
if (ccData1 == 0 && ccData2 == 0) {
|
||||||
continue;
|
continue;
|
||||||
@ -320,26 +236,33 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
captionDataProcessed = true;
|
captionDataProcessed = true;
|
||||||
|
|
||||||
// Special North American character set.
|
// Special North American character set.
|
||||||
|
// ccData1 - P|0|0|1|C|0|0|1
|
||||||
// ccData2 - P|0|1|1|X|X|X|X
|
// ccData2 - P|0|1|1|X|X|X|X
|
||||||
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
|
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
|
||||||
|
// TODO: Make use of the channel bit
|
||||||
captionStringBuilder.append(getSpecialChar(ccData2));
|
captionStringBuilder.append(getSpecialChar(ccData2));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extended Spanish/Miscellaneous and French character set.
|
// Extended Western European character set.
|
||||||
|
// ccData1 - P|0|0|1|C|0|1|S
|
||||||
// ccData2 - P|0|1|X|X|X|X|X
|
// ccData2 - P|0|1|X|X|X|X|X
|
||||||
if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) {
|
if ((ccData2 & 0x60) == 0x20) {
|
||||||
backspace(); // Remove standard equivalent of the special extended char.
|
// Extended Spanish/Miscellaneous and French character set (S = 0).
|
||||||
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
|
if (ccData1 == 0x12 || ccData1 == 0x1A) {
|
||||||
continue;
|
// TODO: Make use of the channel bit
|
||||||
}
|
backspace(); // Remove standard equivalent of the special extended char.
|
||||||
|
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Extended Portuguese and German/Danish character set.
|
// Extended Portuguese and German/Danish character set (S = 1).
|
||||||
// ccData2 - P|0|1|X|X|X|X|X
|
if (ccData1 == 0x13 || ccData1 == 0x1B) {
|
||||||
if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) {
|
// TODO: Make use of the channel bit
|
||||||
backspace(); // Remove standard equivalent of the special extended char.
|
backspace(); // Remove standard equivalent of the special extended char.
|
||||||
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
|
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control character.
|
// Control character.
|
||||||
@ -367,15 +290,17 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
|
|
||||||
private boolean handleCtrl(byte cc1, byte cc2) {
|
private boolean handleCtrl(byte cc1, byte cc2) {
|
||||||
boolean isRepeatableControl = isRepeatable(cc1);
|
boolean isRepeatableControl = isRepeatable(cc1);
|
||||||
if (isRepeatableControl && repeatableControlSet
|
if (isRepeatableControl) {
|
||||||
&& repeatableControlCc1 == cc1
|
if (repeatableControlSet
|
||||||
&& repeatableControlCc2 == cc2) {
|
&& repeatableControlCc1 == cc1
|
||||||
repeatableControlSet = false;
|
&& repeatableControlCc2 == cc2) {
|
||||||
return true;
|
repeatableControlSet = false;
|
||||||
} else if (isRepeatableControl) {
|
return true;
|
||||||
repeatableControlSet = true;
|
} else {
|
||||||
repeatableControlCc1 = cc1;
|
repeatableControlSet = true;
|
||||||
repeatableControlCc2 = cc2;
|
repeatableControlCc1 = cc1;
|
||||||
|
repeatableControlCc2 = cc2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isMiscCode(cc1, cc2)) {
|
if (isMiscCode(cc1, cc2)) {
|
||||||
handleMiscCode(cc2);
|
handleMiscCode(cc2);
|
||||||
@ -526,16 +451,16 @@ public final class Eia608Decoder implements SubtitleDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inspects an sei message to determine whether it contains EIA-608.
|
* Inspects an sei message to determine whether it contains CEA-608.
|
||||||
* <p>
|
* <p>
|
||||||
* The position of {@code payload} is left unchanged.
|
* The position of {@code payload} is left unchanged.
|
||||||
*
|
*
|
||||||
* @param payloadType The payload type of the message.
|
* @param payloadType The payload type of the message.
|
||||||
* @param payloadLength The length of the payload.
|
* @param payloadLength The length of the payload.
|
||||||
* @param payload A {@link ParsableByteArray} containing the payload.
|
* @param payload A {@link ParsableByteArray} containing the payload.
|
||||||
* @return Whether the sei message contains EIA-608.
|
* @return Whether the sei message contains CEA-608.
|
||||||
*/
|
*/
|
||||||
public static boolean isSeiMessageEia608(int payloadType, int payloadLength,
|
public static boolean isSeiMessageCea608(int payloadType, int payloadLength,
|
||||||
ParsableByteArray payload) {
|
ParsableByteArray payload) {
|
||||||
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
|
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
|
||||||
return false;
|
return false;
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.text.cea;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
|
import com.google.android.exoplayer2.text.SubtitleDecoder;
|
||||||
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
|
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
||||||
|
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for subtitle parsers for CEA captions.
|
||||||
|
*/
|
||||||
|
/* package */ abstract class CeaDecoder implements SubtitleDecoder {
|
||||||
|
|
||||||
|
private static final int NUM_INPUT_BUFFERS = 10;
|
||||||
|
private static final int NUM_OUTPUT_BUFFERS = 2;
|
||||||
|
|
||||||
|
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
|
||||||
|
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
||||||
|
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
||||||
|
|
||||||
|
private SubtitleInputBuffer dequeuedInputBuffer;
|
||||||
|
private long playbackPositionUs;
|
||||||
|
|
||||||
|
public CeaDecoder() {
|
||||||
|
availableInputBuffers = new LinkedList<>();
|
||||||
|
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
||||||
|
availableInputBuffers.add(new SubtitleInputBuffer());
|
||||||
|
}
|
||||||
|
availableOutputBuffers = new LinkedList<>();
|
||||||
|
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
||||||
|
availableOutputBuffers.add(new CeaOutputBuffer(this));
|
||||||
|
}
|
||||||
|
queuedInputBuffers = new TreeSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPositionUs(long positionUs) {
|
||||||
|
playbackPositionUs = positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
|
||||||
|
Assertions.checkState(dequeuedInputBuffer == null);
|
||||||
|
if (availableInputBuffers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
dequeuedInputBuffer = availableInputBuffers.pollFirst();
|
||||||
|
return dequeuedInputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
|
||||||
|
Assertions.checkArgument(inputBuffer != null);
|
||||||
|
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
|
||||||
|
queuedInputBuffers.add(inputBuffer);
|
||||||
|
dequeuedInputBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
|
||||||
|
if (availableOutputBuffers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate through all available input buffers whose timestamps are less than or equal
|
||||||
|
// to the current playback position; processing input buffers for future content should
|
||||||
|
// be deferred until they would be applicable
|
||||||
|
while (!queuedInputBuffers.isEmpty()
|
||||||
|
&& queuedInputBuffers.first().timeUs <= playbackPositionUs) {
|
||||||
|
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
|
||||||
|
|
||||||
|
// If the input buffer indicates we've reached the end of the stream, we can
|
||||||
|
// return immediately with an output buffer propagating that
|
||||||
|
if (inputBuffer.isEndOfStream()) {
|
||||||
|
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||||
|
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||||
|
releaseInputBuffer(inputBuffer);
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
decode(inputBuffer);
|
||||||
|
|
||||||
|
// check if we have any caption updates to report
|
||||||
|
if (isNewSubtitleDataAvailable()) {
|
||||||
|
// Even if the subtitle is decode-only; we need to generate it to consume the data so it
|
||||||
|
// isn't accidentally prepended to the next subtitle
|
||||||
|
Subtitle subtitle = createSubtitle();
|
||||||
|
if (!inputBuffer.isDecodeOnly()) {
|
||||||
|
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||||
|
outputBuffer.setContent(inputBuffer.timeUs, subtitle, 0);
|
||||||
|
releaseInputBuffer(inputBuffer);
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseInputBuffer(inputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
|
||||||
|
inputBuffer.clear();
|
||||||
|
availableInputBuffers.add(inputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
|
||||||
|
outputBuffer.clear();
|
||||||
|
availableOutputBuffers.add(outputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
playbackPositionUs = 0;
|
||||||
|
while (!queuedInputBuffers.isEmpty()) {
|
||||||
|
releaseInputBuffer(queuedInputBuffers.pollFirst());
|
||||||
|
}
|
||||||
|
if (dequeuedInputBuffer != null) {
|
||||||
|
releaseInputBuffer(dequeuedInputBuffer);
|
||||||
|
dequeuedInputBuffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether there is data available to create a new {@link Subtitle}.
|
||||||
|
*/
|
||||||
|
protected abstract boolean isNewSubtitleDataAvailable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Subtitle} from the available data.
|
||||||
|
*/
|
||||||
|
protected abstract Subtitle createSubtitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()}
|
||||||
|
* when sufficient data has been processed.
|
||||||
|
*/
|
||||||
|
protected abstract void decode(SubtitleInputBuffer inputBuffer);
|
||||||
|
|
||||||
|
}
|
@ -13,22 +13,21 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.eia608;
|
package com.google.android.exoplayer2.text.cea;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
|
||||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Subtitle} output from an {@link Eia608Decoder}.
|
* A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s.
|
||||||
*/
|
*/
|
||||||
/* package */ final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer {
|
public final class CeaOutputBuffer extends SubtitleOutputBuffer {
|
||||||
|
|
||||||
private Eia608Decoder owner;
|
private final CeaDecoder owner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param owner The decoder that owns this buffer.
|
* @param owner The decoder that owns this buffer.
|
||||||
*/
|
*/
|
||||||
public Eia608SubtitleOutputBuffer(Eia608Decoder owner) {
|
public CeaOutputBuffer(CeaDecoder owner) {
|
||||||
super();
|
super();
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
@ -13,26 +13,29 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.eia608;
|
package com.google.android.exoplayer2.text.cea;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of an EIA-608 subtitle.
|
* A representation of a CEA subtitle.
|
||||||
*/
|
*/
|
||||||
/* package */ final class Eia608Subtitle implements Subtitle {
|
/* package */ final class CeaSubtitle implements Subtitle {
|
||||||
|
|
||||||
private final String text;
|
private final List<Cue> cues;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param text The subtitle text.
|
* @param cue The subtitle cue.
|
||||||
*/
|
*/
|
||||||
public Eia608Subtitle(String text) {
|
public CeaSubtitle(Cue cue) {
|
||||||
this.text = text;
|
if (cue == null) {
|
||||||
|
cues = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
cues = Collections.singletonList(cue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -52,11 +55,8 @@ import java.util.List;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCues(long timeUs) {
|
public List<Cue> getCues(long timeUs) {
|
||||||
if (TextUtils.isEmpty(text)) {
|
return cues;
|
||||||
return Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
return Collections.singletonList(new Cue(text));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
|
|
||||||
public final float position;
|
public final float position;
|
||||||
public final float line;
|
public final float line;
|
||||||
|
@Cue.LineType
|
||||||
public final int lineType;
|
public final int lineType;
|
||||||
public final float width;
|
public final float width;
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlRegion(float position, float line, int lineType, float width) {
|
public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.lineType = lineType;
|
this.lineType = lineType;
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style object of a <code>TtmlNode</code>
|
* Style object of a <code>TtmlNode</code>
|
||||||
@ -26,15 +29,25 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
|
|
||||||
public static final int UNSPECIFIED = -1;
|
public static final int UNSPECIFIED = -1;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
|
||||||
|
STYLE_BOLD_ITALIC})
|
||||||
|
public @interface StyleFlags {}
|
||||||
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
||||||
public static final int STYLE_BOLD = Typeface.BOLD;
|
public static final int STYLE_BOLD = Typeface.BOLD;
|
||||||
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
||||||
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
|
||||||
|
public @interface FontSizeUnit {}
|
||||||
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
||||||
public static final int FONT_SIZE_UNIT_EM = 2;
|
public static final int FONT_SIZE_UNIT_EM = 2;
|
||||||
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({UNSPECIFIED, OFF, ON})
|
||||||
|
private @interface OptionalBoolean {}
|
||||||
private static final int OFF = 0;
|
private static final int OFF = 0;
|
||||||
private static final int ON = 1;
|
private static final int ON = 1;
|
||||||
|
|
||||||
@ -43,10 +56,15 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
private boolean hasFontColor;
|
private boolean hasFontColor;
|
||||||
private int backgroundColor;
|
private int backgroundColor;
|
||||||
private boolean hasBackgroundColor;
|
private boolean hasBackgroundColor;
|
||||||
|
@OptionalBoolean
|
||||||
private int linethrough;
|
private int linethrough;
|
||||||
|
@OptionalBoolean
|
||||||
private int underline;
|
private int underline;
|
||||||
|
@OptionalBoolean
|
||||||
private int bold;
|
private int bold;
|
||||||
|
@OptionalBoolean
|
||||||
private int italic;
|
private int italic;
|
||||||
|
@FontSizeUnit
|
||||||
private int fontSizeUnit;
|
private int fontSizeUnit;
|
||||||
private float fontSize;
|
private float fontSize;
|
||||||
private String id;
|
private String id;
|
||||||
@ -67,12 +85,13 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||||
* or {@link #STYLE_BOLD_ITALIC}.
|
* or {@link #STYLE_BOLD_ITALIC}.
|
||||||
*/
|
*/
|
||||||
|
@StyleFlags
|
||||||
public int getStyle() {
|
public int getStyle() {
|
||||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||||
return UNSPECIFIED;
|
return UNSPECIFIED;
|
||||||
}
|
}
|
||||||
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
|
||||||
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
| (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLinethrough() {
|
public boolean isLinethrough() {
|
||||||
@ -95,6 +114,18 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setBold(boolean bold) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.bold = bold ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setItalic(boolean italic) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.italic = italic ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFontFamily() {
|
public String getFontFamily() {
|
||||||
return fontFamily;
|
return fontFamily;
|
||||||
}
|
}
|
||||||
@ -140,18 +171,6 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return hasBackgroundColor;
|
return hasBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setBold(boolean isBold) {
|
|
||||||
Assertions.checkState(inheritableStyle == null);
|
|
||||||
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TtmlStyle setItalic(boolean isItalic) {
|
|
||||||
Assertions.checkState(inheritableStyle == null);
|
|
||||||
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
|
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
|
||||||
* are not inheritable are not inherited as well as properties which are already set locally
|
* are not inheritable are not inherited as well as properties which are already set locally
|
||||||
@ -236,6 +255,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FontSizeUnit
|
||||||
public int getFontSizeUnit() {
|
public int getFontSizeUnit() {
|
||||||
return fontSizeUnit;
|
return fontSizeUnit;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
package com.google.android.exoplayer2.text.webvtt;
|
package com.google.android.exoplayer2.text.webvtt;
|
||||||
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -32,15 +35,25 @@ import java.util.List;
|
|||||||
|
|
||||||
public static final int UNSPECIFIED = -1;
|
public static final int UNSPECIFIED = -1;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
|
||||||
|
STYLE_BOLD_ITALIC})
|
||||||
|
public @interface StyleFlags {}
|
||||||
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
public static final int STYLE_NORMAL = Typeface.NORMAL;
|
||||||
public static final int STYLE_BOLD = Typeface.BOLD;
|
public static final int STYLE_BOLD = Typeface.BOLD;
|
||||||
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
public static final int STYLE_ITALIC = Typeface.ITALIC;
|
||||||
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
|
||||||
|
public @interface FontSizeUnit {}
|
||||||
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
public static final int FONT_SIZE_UNIT_PIXEL = 1;
|
||||||
public static final int FONT_SIZE_UNIT_EM = 2;
|
public static final int FONT_SIZE_UNIT_EM = 2;
|
||||||
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
public static final int FONT_SIZE_UNIT_PERCENT = 3;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({UNSPECIFIED, OFF, ON})
|
||||||
|
private @interface OptionalBoolean {}
|
||||||
private static final int OFF = 0;
|
private static final int OFF = 0;
|
||||||
private static final int ON = 1;
|
private static final int ON = 1;
|
||||||
|
|
||||||
@ -56,10 +69,15 @@ import java.util.List;
|
|||||||
private boolean hasFontColor;
|
private boolean hasFontColor;
|
||||||
private int backgroundColor;
|
private int backgroundColor;
|
||||||
private boolean hasBackgroundColor;
|
private boolean hasBackgroundColor;
|
||||||
|
@OptionalBoolean
|
||||||
private int linethrough;
|
private int linethrough;
|
||||||
|
@OptionalBoolean
|
||||||
private int underline;
|
private int underline;
|
||||||
|
@OptionalBoolean
|
||||||
private int bold;
|
private int bold;
|
||||||
|
@OptionalBoolean
|
||||||
private int italic;
|
private int italic;
|
||||||
|
@FontSizeUnit
|
||||||
private int fontSizeUnit;
|
private int fontSizeUnit;
|
||||||
private float fontSize;
|
private float fontSize;
|
||||||
private Layout.Alignment textAlign;
|
private Layout.Alignment textAlign;
|
||||||
@ -144,12 +162,13 @@ import java.util.List;
|
|||||||
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||||
* or {@link #STYLE_BOLD_ITALIC}.
|
* or {@link #STYLE_BOLD_ITALIC}.
|
||||||
*/
|
*/
|
||||||
|
@StyleFlags
|
||||||
public int getStyle() {
|
public int getStyle() {
|
||||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||||
return UNSPECIFIED;
|
return UNSPECIFIED;
|
||||||
}
|
}
|
||||||
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
return (bold == ON ? STYLE_BOLD : STYLE_NORMAL)
|
||||||
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
| (italic == ON ? STYLE_ITALIC : STYLE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLinethrough() {
|
public boolean isLinethrough() {
|
||||||
@ -169,6 +188,15 @@ import java.util.List;
|
|||||||
this.underline = underline ? ON : OFF;
|
this.underline = underline ? ON : OFF;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public WebvttCssStyle setBold(boolean bold) {
|
||||||
|
this.bold = bold ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebvttCssStyle setItalic(boolean italic) {
|
||||||
|
this.italic = italic ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFontFamily() {
|
public String getFontFamily() {
|
||||||
return fontFamily;
|
return fontFamily;
|
||||||
@ -213,16 +241,6 @@ import java.util.List;
|
|||||||
return hasBackgroundColor;
|
return hasBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebvttCssStyle setBold(boolean isBold) {
|
|
||||||
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebvttCssStyle setItalic(boolean isItalic) {
|
|
||||||
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Layout.Alignment getTextAlign() {
|
public Layout.Alignment getTextAlign() {
|
||||||
return textAlign;
|
return textAlign;
|
||||||
}
|
}
|
||||||
@ -242,6 +260,7 @@ import java.util.List;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FontSizeUnit
|
||||||
public int getFontSizeUnit() {
|
public int getFontSizeUnit() {
|
||||||
return fontSizeUnit;
|
return fontSizeUnit;
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,9 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment,
|
||||||
float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) {
|
float line, @Cue.LineType int lineType, @Cue.AnchorType int lineAnchor, float position,
|
||||||
|
@Cue.AnchorType int positionAnchor, float width) {
|
||||||
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
|
||||||
this.startTime = startTime;
|
this.startTime = startTime;
|
||||||
this.endTime = endTime;
|
this.endTime = endTime;
|
||||||
|
@ -18,22 +18,301 @@ package com.google.android.exoplayer2.trackselection;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.text.TextUtils;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MappingTrackSelector} that allows configuration of common parameters.
|
* A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call
|
||||||
|
* the methods of this class from the application thread. See {@link Parameters#Parameters()} for
|
||||||
|
* default selection parameters.
|
||||||
*/
|
*/
|
||||||
public class DefaultTrackSelector extends MappingTrackSelector {
|
public class DefaultTrackSelector extends MappingTrackSelector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for available configurations for the {@link DefaultTrackSelector}.
|
||||||
|
*/
|
||||||
|
public static final class Parameters {
|
||||||
|
|
||||||
|
// Audio.
|
||||||
|
public final String preferredAudioLanguage;
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
public final String preferredTextLanguage;
|
||||||
|
|
||||||
|
// Video.
|
||||||
|
public final boolean allowMixedMimeAdaptiveness;
|
||||||
|
public final boolean allowNonSeamlessAdaptiveness;
|
||||||
|
public final int maxVideoWidth;
|
||||||
|
public final int maxVideoHeight;
|
||||||
|
public final boolean exceedVideoConstraintsIfNecessary;
|
||||||
|
public final int viewportWidth;
|
||||||
|
public final int viewportHeight;
|
||||||
|
public final boolean orientationMayChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default selection parameters:
|
||||||
|
* <ul>
|
||||||
|
* <li>No preferred audio language is set.</li>
|
||||||
|
* <li>No preferred text language is set.</li>
|
||||||
|
* <li>Adaptation between different mime types is not allowed.</li>
|
||||||
|
* <li>Non seamless adaptation is allowed.</li>
|
||||||
|
* <li>No max limit for video width/height.</li>
|
||||||
|
* <li>Video constraints are ignored if no supported selection can be made otherwise.</li>
|
||||||
|
* <li>No viewport width/height constraints are set.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public Parameters() {
|
||||||
|
this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE,
|
||||||
|
Integer.MAX_VALUE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param preferredAudioLanguage The preferred language for audio, as well as for forced text
|
||||||
|
* tracks as defined by RFC 5646. {@code null} to select the default track, or first track
|
||||||
|
* if there's no default.
|
||||||
|
* @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646.
|
||||||
|
* {@code null} to select the default track, or first track if there's no default.
|
||||||
|
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
||||||
|
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
||||||
|
* @param maxVideoWidth Maximum allowed video width.
|
||||||
|
* @param maxVideoHeight Maximum allowed video height.
|
||||||
|
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
||||||
|
* can be made otherwise. False to force constraints anyway.
|
||||||
|
* @param viewportWidth Viewport width in pixels.
|
||||||
|
* @param viewportHeight Viewport height in pixels.
|
||||||
|
* @param orientationMayChange Whether orientation may change during playback.
|
||||||
|
*/
|
||||||
|
public Parameters(String preferredAudioLanguage, String preferredTextLanguage,
|
||||||
|
boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness,
|
||||||
|
int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary,
|
||||||
|
int viewportWidth, int viewportHeight, boolean orientationMayChange) {
|
||||||
|
this.preferredAudioLanguage = preferredAudioLanguage;
|
||||||
|
this.preferredTextLanguage = preferredTextLanguage;
|
||||||
|
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
|
||||||
|
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
|
||||||
|
this.maxVideoWidth = maxVideoWidth;
|
||||||
|
this.maxVideoHeight = maxVideoHeight;
|
||||||
|
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
|
||||||
|
this.viewportWidth = viewportWidth;
|
||||||
|
this.viewportHeight = viewportHeight;
|
||||||
|
this.orientationMayChange = orientationMayChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided preferred language for audio and
|
||||||
|
* forced text tracks.
|
||||||
|
*
|
||||||
|
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||||
|
* select the default track, or first track if there's no default.
|
||||||
|
* @return A {@link Parameters} instance with the provided preferred language for audio and
|
||||||
|
* forced text tracks.
|
||||||
|
*/
|
||||||
|
public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) {
|
||||||
|
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
|
||||||
|
if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight,
|
||||||
|
exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided preferred language for text tracks.
|
||||||
|
*
|
||||||
|
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
|
||||||
|
* select the default track, or no track if there's no default.
|
||||||
|
* @return A {@link Parameters} instance with the provided preferred language for text tracks.
|
||||||
|
*/
|
||||||
|
public Parameters withPreferredTextLanguage(String preferredTextLanguage) {
|
||||||
|
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
|
||||||
|
if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
|
||||||
|
*
|
||||||
|
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
||||||
|
* @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
|
||||||
|
*/
|
||||||
|
public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
|
||||||
|
if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance.
|
||||||
|
*
|
||||||
|
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
||||||
|
* @return A {@link Parameters} instance with the provided seamless adaptiveness allowance.
|
||||||
|
*/
|
||||||
|
public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
|
||||||
|
if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided max video size.
|
||||||
|
*
|
||||||
|
* @param maxVideoWidth The max video width.
|
||||||
|
* @param maxVideoHeight The max video width.
|
||||||
|
* @return A {@link Parameters} instance with the provided max video size.
|
||||||
|
*/
|
||||||
|
public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
|
||||||
|
if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code withMaxVideoSize(1279, 719)}.
|
||||||
|
*
|
||||||
|
* @return A {@link Parameters} instance with maximum standard definition as maximum video size.
|
||||||
|
*/
|
||||||
|
public Parameters withMaxVideoSizeSd() {
|
||||||
|
return withMaxVideoSize(1279, 719);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
|
||||||
|
*
|
||||||
|
* @return A {@link Parameters} instance without video size constraints.
|
||||||
|
*/
|
||||||
|
public Parameters withoutVideoSizeConstraints() {
|
||||||
|
return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided
|
||||||
|
* {@code exceedVideoConstraintsIfNecessary} value.
|
||||||
|
*
|
||||||
|
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
||||||
|
* can be made otherwise. False to force constraints anyway.
|
||||||
|
* @return A {@link Parameters} instance with the provided
|
||||||
|
* {@code exceedVideoConstraintsIfNecessary} value.
|
||||||
|
*/
|
||||||
|
public Parameters withExceedVideoConstraintsIfNecessary(
|
||||||
|
boolean exceedVideoConstraintsIfNecessary) {
|
||||||
|
if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance with the provided viewport size.
|
||||||
|
*
|
||||||
|
* @param viewportWidth Viewport width in pixels.
|
||||||
|
* @param viewportHeight Viewport height in pixels.
|
||||||
|
* @param orientationMayChange Whether orientation may change during playback.
|
||||||
|
* @return A {@link Parameters} instance with the provided viewport size.
|
||||||
|
*/
|
||||||
|
public Parameters withViewportSize(int viewportWidth, int viewportHeight,
|
||||||
|
boolean orientationMayChange) {
|
||||||
|
if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight
|
||||||
|
&& orientationMayChange == this.orientationMayChange) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
|
||||||
|
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
|
||||||
|
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
|
||||||
|
orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Parameters} instance where the viewport size is obtained from the provided
|
||||||
|
* {@link Context}.
|
||||||
|
*
|
||||||
|
* @param context The context to obtain the viewport size from.
|
||||||
|
* @param orientationMayChange Whether orientation may change during playback.
|
||||||
|
* @return A {@link Parameters} instance where the viewport size is obtained from the provided
|
||||||
|
* {@link Context}.
|
||||||
|
*/
|
||||||
|
public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) {
|
||||||
|
// Assume the viewport is fullscreen.
|
||||||
|
Point viewportSize = Util.getPhysicalDisplaySize(context);
|
||||||
|
return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
|
||||||
|
*
|
||||||
|
* @return A {@link Parameters} instance without viewport size constraints.
|
||||||
|
*/
|
||||||
|
public Parameters withoutViewportSizeConstraints() {
|
||||||
|
return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Parameters other = (Parameters) obj;
|
||||||
|
return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness
|
||||||
|
&& allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness
|
||||||
|
&& maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight
|
||||||
|
&& exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary
|
||||||
|
&& orientationMayChange == other.orientationMayChange
|
||||||
|
&& viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
|
||||||
|
&& TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage)
|
||||||
|
&& TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = preferredAudioLanguage.hashCode();
|
||||||
|
result = 31 * result + preferredTextLanguage.hashCode();
|
||||||
|
result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0);
|
||||||
|
result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0);
|
||||||
|
result = 31 * result + maxVideoWidth;
|
||||||
|
result = 31 * result + maxVideoHeight;
|
||||||
|
result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0);
|
||||||
|
result = 31 * result + (orientationMayChange ? 1 : 0);
|
||||||
|
result = 31 * result + viewportWidth;
|
||||||
|
result = 31 * result + viewportHeight;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
|
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
|
||||||
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
|
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
|
||||||
@ -43,22 +322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
private static final int[] NO_TRACKS = new int[0];
|
private static final int[] NO_TRACKS = new int[0];
|
||||||
|
|
||||||
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
|
||||||
|
private final AtomicReference<Parameters> params;
|
||||||
// Audio.
|
|
||||||
private String preferredAudioLanguage;
|
|
||||||
|
|
||||||
// Text.
|
|
||||||
private String preferredTextLanguage;
|
|
||||||
|
|
||||||
// Video.
|
|
||||||
private boolean allowMixedMimeAdaptiveness;
|
|
||||||
private boolean allowNonSeamlessAdaptiveness;
|
|
||||||
private int maxVideoWidth;
|
|
||||||
private int maxVideoHeight;
|
|
||||||
private boolean exceedVideoConstraintsIfNecessary;
|
|
||||||
private boolean orientationMayChange;
|
|
||||||
private int viewportWidth;
|
|
||||||
private int viewportHeight;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance that does not support adaptive video.
|
* Constructs an instance that does not support adaptive video.
|
||||||
@ -82,142 +346,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
|
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
|
||||||
super(eventHandler);
|
super(eventHandler);
|
||||||
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
|
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
|
||||||
allowNonSeamlessAdaptiveness = true;
|
params = new AtomicReference<>(new Parameters());
|
||||||
exceedVideoConstraintsIfNecessary = true;
|
|
||||||
maxVideoWidth = Integer.MAX_VALUE;
|
|
||||||
maxVideoHeight = Integer.MAX_VALUE;
|
|
||||||
viewportWidth = Integer.MAX_VALUE;
|
|
||||||
viewportHeight = Integer.MAX_VALUE;
|
|
||||||
orientationMayChange = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the preferred language for audio, as well as for forced text tracks.
|
* Atomically sets the provided parameters for track selection.
|
||||||
*
|
*
|
||||||
* @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to
|
* @param params The parameters for track selection.
|
||||||
* select the default track, or first track if there's no default.
|
|
||||||
*/
|
*/
|
||||||
public void setPreferredLanguages(String preferredAudioLanguage) {
|
public void setParameters(Parameters params) {
|
||||||
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
|
if (!this.params.get().equals(params)) {
|
||||||
if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) {
|
this.params.set(Assertions.checkNotNull(params));
|
||||||
this.preferredAudioLanguage = preferredAudioLanguage;
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the preferred language for text tracks.
|
* Gets the current selection parameters.
|
||||||
*
|
*
|
||||||
* @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to
|
* @return The current selection parameters.
|
||||||
* select the default track, or no track if there's no default.
|
|
||||||
*/
|
*/
|
||||||
public void setPreferredTextLanguage(String preferredTextLanguage) {
|
public Parameters getParameters() {
|
||||||
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
|
return params.get();
|
||||||
if (!Util.areEqual(this.preferredTextLanguage, preferredTextLanguage)) {
|
|
||||||
this.preferredTextLanguage = preferredTextLanguage;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether to allow selections to contain mixed mime types.
|
|
||||||
*
|
|
||||||
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
|
|
||||||
*/
|
|
||||||
public void allowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
|
|
||||||
if (this.allowMixedMimeAdaptiveness != allowMixedMimeAdaptiveness) {
|
|
||||||
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether non-seamless adaptation is allowed.
|
|
||||||
*
|
|
||||||
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
|
|
||||||
*/
|
|
||||||
public void allowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
|
|
||||||
if (this.allowNonSeamlessAdaptiveness != allowNonSeamlessAdaptiveness) {
|
|
||||||
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the maximum allowed size for video tracks.
|
|
||||||
*
|
|
||||||
* @param maxVideoWidth Maximum allowed width.
|
|
||||||
* @param maxVideoHeight Maximum allowed height.
|
|
||||||
*/
|
|
||||||
public void setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
|
|
||||||
if (this.maxVideoWidth != maxVideoWidth || this.maxVideoHeight != maxVideoHeight) {
|
|
||||||
this.maxVideoWidth = maxVideoWidth;
|
|
||||||
this.maxVideoHeight = maxVideoHeight;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equivalent to {@code setMaxVideoSize(1279, 719)}.
|
|
||||||
*/
|
|
||||||
public void setMaxVideoSizeSd() {
|
|
||||||
setMaxVideoSize(1279, 719);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equivalent to {@code setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
|
|
||||||
*/
|
|
||||||
public void clearMaxVideoSize() {
|
|
||||||
setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether video constraints should be ignored when no selection can be made otherwise.
|
|
||||||
*
|
|
||||||
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
|
|
||||||
* can be made otherwise. False to force constraints anyway.
|
|
||||||
*/
|
|
||||||
public void setExceedVideoConstraintsIfNecessary(boolean exceedVideoConstraintsIfNecessary) {
|
|
||||||
if (this.exceedVideoConstraintsIfNecessary != exceedVideoConstraintsIfNecessary) {
|
|
||||||
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the target viewport size for selecting video tracks.
|
|
||||||
*
|
|
||||||
* @param viewportWidth Viewport width in pixels.
|
|
||||||
* @param viewportHeight Viewport height in pixels.
|
|
||||||
* @param orientationMayChange Whether orientation may change during playback.
|
|
||||||
*/
|
|
||||||
public void setViewportSize(int viewportWidth, int viewportHeight, boolean orientationMayChange) {
|
|
||||||
if (this.viewportWidth != viewportWidth || this.viewportHeight != viewportHeight
|
|
||||||
|| this.orientationMayChange != orientationMayChange) {
|
|
||||||
this.viewportWidth = viewportWidth;
|
|
||||||
this.viewportHeight = viewportHeight;
|
|
||||||
this.orientationMayChange = orientationMayChange;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the viewport size from the provided {@link Context} and calls
|
|
||||||
* {@link #setViewportSize(int, int, boolean)} with this information.
|
|
||||||
*
|
|
||||||
* @param context The context to obtain the viewport size from.
|
|
||||||
* @param orientationMayChange Whether orientation may change during playback.
|
|
||||||
*/
|
|
||||||
public void setViewportSizeFromContext(Context context, boolean orientationMayChange) {
|
|
||||||
Point viewportSize = Util.getPhysicalDisplaySize(context); // Assume the viewport is fullscreen.
|
|
||||||
setViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
|
|
||||||
*/
|
|
||||||
public void clearViewportConstraints() {
|
|
||||||
setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MappingTrackSelector implementation.
|
// MappingTrackSelector implementation.
|
||||||
@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
// Make a track selection for each renderer.
|
// Make a track selection for each renderer.
|
||||||
TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length];
|
TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length];
|
||||||
|
Parameters params = this.params.get();
|
||||||
for (int i = 0; i < rendererCapabilities.length; i++) {
|
for (int i = 0; i < rendererCapabilities.length; i++) {
|
||||||
switch (rendererCapabilities[i].getTrackType()) {
|
switch (rendererCapabilities[i].getTrackType()) {
|
||||||
case C.TRACK_TYPE_VIDEO:
|
case C.TRACK_TYPE_VIDEO:
|
||||||
rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i],
|
rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i],
|
||||||
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight,
|
rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth,
|
||||||
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth,
|
params.maxVideoHeight, params.allowNonSeamlessAdaptiveness,
|
||||||
viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory,
|
params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight,
|
||||||
exceedVideoConstraintsIfNecessary);
|
params.orientationMayChange, adaptiveVideoTrackSelectionFactory,
|
||||||
|
params.exceedVideoConstraintsIfNecessary);
|
||||||
break;
|
break;
|
||||||
case C.TRACK_TYPE_AUDIO:
|
case C.TRACK_TYPE_AUDIO:
|
||||||
rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i],
|
rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i],
|
||||||
rendererFormatSupports[i], preferredAudioLanguage);
|
rendererFormatSupports[i], params.preferredAudioLanguage);
|
||||||
break;
|
break;
|
||||||
case C.TRACK_TYPE_TEXT:
|
case C.TRACK_TYPE_TEXT:
|
||||||
rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i],
|
rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i],
|
||||||
rendererFormatSupports[i], preferredTextLanguage, preferredAudioLanguage);
|
rendererFormatSupports[i], params.preferredTextLanguage,
|
||||||
|
params.preferredAudioLanguage);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(),
|
rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(),
|
||||||
@ -442,7 +595,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||||
Format format = trackGroup.getFormat(trackIndex);
|
Format format = trackGroup.getFormat(trackIndex);
|
||||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||||
int trackScore;
|
int trackScore;
|
||||||
if (formatHasLanguage(format, preferredAudioLanguage)) {
|
if (formatHasLanguage(format, preferredAudioLanguage)) {
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
@ -480,8 +633,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||||
Format format = trackGroup.getFormat(trackIndex);
|
Format format = trackGroup.getFormat(trackIndex);
|
||||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||||
boolean isForced = (format.selectionFlags & Format.SELECTION_FLAG_FORCED) != 0;
|
boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0;
|
||||||
int trackScore;
|
int trackScore;
|
||||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
@ -530,7 +683,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||||
if (isSupported(trackFormatSupport[trackIndex])) {
|
if (isSupported(trackFormatSupport[trackIndex])) {
|
||||||
Format format = trackGroup.getFormat(trackIndex);
|
Format format = trackGroup.getFormat(trackIndex);
|
||||||
boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0;
|
boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
|
||||||
int trackScore = isDefault ? 2 : 1;
|
int trackScore = isDefault ? 2 : 1;
|
||||||
if (trackScore > selectedTrackScore) {
|
if (trackScore > selectedTrackScore) {
|
||||||
selectedGroup = trackGroup;
|
selectedGroup = trackGroup;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user