Commit f8a8302f by ojw28 Committed by GitHub

Merge pull request #1879 from google/dev-v2

r2.0.1
parents 7d991cef c381093a
Showing with 3270 additions and 1925 deletions
...@@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates. ...@@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates.
#### Via jCenter #### #### Via jCenter ####
The easiest way to get started using ExoPlayer is by including the following in The easiest way to get started using ExoPlayer is to add it as a gradle
your project's `build.gradle` file: dependency. You need to make sure you have the jcenter repository included in
the `build.gradle` file in the root of your project:
```gradle
repositories {
jcenter()
}
```
Next, include the following in your module's `build.gradle` file:
```gradle ```gradle
compile 'com.google.android.exoplayer:exoplayer:rX.X.X' compile 'com.google.android.exoplayer:exoplayer:rX.X.X'
......
# Release notes # # Release notes #
### r2.0.1 ###
* Fix playback of short duration content
([#1837](https://github.com/google/ExoPlayer/issues/1837)).
* Fix MergingMediaSource preparation issue
([#1853](https://github.com/google/ExoPlayer/issues/1853)).
* Fix live stream buffering (out of memory) issue
([#1825](https://github.com/google/ExoPlayer/issues/1825)).
### r2.0.0 ### ### r2.0.0 ###
ExoPlayer 2.x is a major iteration of the library. It includes significant API ExoPlayer 2.x is a major iteration of the library. It includes significant API
......
...@@ -39,14 +39,14 @@ android { ...@@ -39,14 +39,14 @@ android {
productFlavors { productFlavors {
demo demo
demo_ext demoExt
} }
} }
dependencies { dependencies {
compile project(':library') compile project(':library')
demo_extCompile project(path: ':extension-ffmpeg') demoExtCompile project(path: ':extension-ffmpeg')
demo_extCompile project(path: ':extension-flac') demoExtCompile project(path: ':extension-flac')
demo_extCompile project(path: ':extension-opus') demoExtCompile project(path: ':extension-opus')
demo_extCompile project(path: ':extension-vp9') demoExtCompile project(path: ':extension-vp9')
} }
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2000" android:versionCode="2001"
android:versionName="2.0.0"> android:versionName="2.0.1">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......
...@@ -16,9 +16,33 @@ ...@@ -16,9 +16,33 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Application; import android.app.Application;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
/** /**
* Placeholder application to facilitate overriding Application methods for debugging and testing. * Placeholder application to facilitate overriding Application methods for debugging and testing.
*/ */
public class DemoApplication extends Application { public class DemoApplication extends Application {
protected String userAgent;
@Override
public void onCreate() {
super.onCreate();
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
}
DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultDataSourceFactory(this, bandwidthMeter,
buildHttpDataSourceFactory(bandwidthMeter));
}
HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
}
} }
...@@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; ...@@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException; import java.io.IOException;
...@@ -54,7 +55,7 @@ import java.util.Locale; ...@@ -54,7 +55,7 @@ import java.util.Locale;
/* package */ final class EventLogger implements ExoPlayer.EventListener, /* package */ final class EventLogger implements ExoPlayer.EventListener,
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener, AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener,
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> { TrackSelector.EventListener<MappedTrackInfo>, MetadataRenderer.Output<List<Id3Frame>> {
private static final String TAG = "EventLogger"; private static final String TAG = "EventLogger";
private static final int MAX_TIMELINE_ITEM_LINES = 3; private static final int MAX_TIMELINE_ITEM_LINES = 3;
...@@ -125,23 +126,24 @@ import java.util.Locale; ...@@ -125,23 +126,24 @@ import java.util.Locale;
// MappingTrackSelector.EventListener // MappingTrackSelector.EventListener
@Override @Override
public void onTracksChanged(TrackInfo trackInfo) { public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
Log.d(TAG, "Tracks ["); Log.d(TAG, "Tracks [");
// Log tracks associated to renderers. // Log tracks associated to renderers.
for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) { MappedTrackInfo info = trackSelections.info;
TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex); for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) {
TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex); TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex);
TrackSelection trackSelection = trackSelections.get(rendererIndex);
if (trackGroups.length > 0) { if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:" + rendererIndex + " ["); Log.d(TAG, " Renderer:" + rendererIndex + " [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = trackGroups.get(groupIndex);
String adaptiveSupport = getAdaptiveSupportString( String adaptiveSupport = getAdaptiveSupportString(
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)); trackGroup.length, info.getAdaptiveSupport(rendererIndex, groupIndex, false));
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " ["); Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
String formatSupport = getFormatSupportString( String formatSupport = getFormatSupportString(
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
Log.d(TAG, " " + status + " Track:" + trackIndex + ", " Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
+ getFormatString(trackGroup.getFormat(trackIndex)) + getFormatString(trackGroup.getFormat(trackIndex))
+ ", supported=" + formatSupport); + ", supported=" + formatSupport);
...@@ -152,7 +154,7 @@ import java.util.Locale; ...@@ -152,7 +154,7 @@ import java.util.Locale;
} }
} }
// Log tracks not associated with a renderer. // Log tracks not associated with a renderer.
TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups(); TrackGroupArray trackGroups = info.getUnassociatedTrackGroups();
if (trackGroups.length > 0) { if (trackGroups.length > 0) {
Log.d(TAG, " Renderer:None ["); Log.d(TAG, " Renderer:None [");
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
......
...@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
...@@ -55,15 +56,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; ...@@ -55,15 +56,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.DebugTextViewHelper; import com.google.android.exoplayer2.ui.DebugTextViewHelper;
import com.google.android.exoplayer2.ui.PlaybackControlView; import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView; import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler; import java.net.CookieHandler;
...@@ -77,7 +78,7 @@ import java.util.UUID; ...@@ -77,7 +78,7 @@ import java.util.UUID;
* An activity that plays media using {@link SimpleExoPlayer}. * An activity that plays media using {@link SimpleExoPlayer}.
*/ */
public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener, public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener,
MappingTrackSelector.EventListener, PlaybackControlView.VisibilityListener { TrackSelector.EventListener<MappedTrackInfo>, PlaybackControlView.VisibilityListener {
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
public static final String DRM_LICENSE_URL = "drm_license_url"; public static final String DRM_LICENSE_URL = "drm_license_url";
...@@ -106,7 +107,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -106,7 +107,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
private TextView debugTextView; private TextView debugTextView;
private Button retryButton; private Button retryButton;
private String userAgent;
private DataSource.Factory mediaDataSourceFactory; private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private MappingTrackSelector trackSelector; private MappingTrackSelector trackSelector;
...@@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
shouldAutoPlay = true; shouldAutoPlay = true;
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
mediaDataSourceFactory = buildDataSourceFactory(true); mediaDataSourceFactory = buildDataSourceFactory(true);
mainHandler = new Handler(); mainHandler = new Handler();
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
...@@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
initializePlayer(); initializePlayer();
} else if (view.getParent() == debugRootView) { } else if (view.getParent() == debugRootView) {
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(), trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
trackSelector.getTrackInfo(), (int) view.getTag()); trackSelector.getCurrentSelections().info, (int) view.getTag());
} }
} }
...@@ -222,7 +221,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -222,7 +221,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA) UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null; ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
DrmSessionManager drmSessionManager = null; DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
if (drmSchemeUuid != null) { if (drmSchemeUuid != null) {
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL); String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES); String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
...@@ -316,15 +315,15 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -316,15 +315,15 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment()); : uri.getLastPathSegment());
switch (type) { switch (type) {
case Util.TYPE_SS: case C.TYPE_SS:
return new SsMediaSource(uri, buildDataSourceFactory(false), return new SsMediaSource(uri, buildDataSourceFactory(false),
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
case Util.TYPE_DASH: case C.TYPE_DASH:
return new DashMediaSource(uri, buildDataSourceFactory(false), return new DashMediaSource(uri, buildDataSourceFactory(false),
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
case Util.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger); return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
case Util.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, eventLogger); mainHandler, eventLogger);
default: { default: {
...@@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
} }
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
Map<String, String> keyRequestProperties) String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
throws UnsupportedDrmException {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
return null; return null;
} }
...@@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
* @return A new DataSource factory. * @return A new DataSource factory.
*/ */
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
return new DefaultDataSourceFactory(this, useBandwidthMeter ? BANDWIDTH_METER : null, return ((DemoApplication) getApplication())
buildHttpDataSourceFactory(useBandwidthMeter)); .buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
} }
/** /**
...@@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
* @return A new HttpDataSource factory. * @return A new HttpDataSource factory.
*/ */
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null); return ((DemoApplication) getApplication())
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
} }
// ExoPlayer.EventListener implementation // ExoPlayer.EventListener implementation
...@@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
// MappingTrackSelector.EventListener implementation // MappingTrackSelector.EventListener implementation
@Override @Override
public void onTracksChanged(TrackInfo trackInfo) { public void onTrackSelectionsChanged(TrackSelections<? extends MappedTrackInfo> trackSelections) {
updateButtonVisibilities(); updateButtonVisibilities();
MappedTrackInfo trackInfo = trackSelections.info;
if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) {
showToast(R.string.error_unsupported_video); showToast(R.string.error_unsupported_video);
} }
...@@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
return; return;
} }
TrackInfo trackInfo = trackSelector.getTrackInfo(); TrackSelections<MappedTrackInfo> trackSelections = trackSelector.getCurrentSelections();
if (trackInfo == null) { if (trackSelections == null) {
return; return;
} }
int rendererCount = trackInfo.rendererCount; int rendererCount = trackSelections.length;
for (int i = 0; i < rendererCount; i++) { for (int i = 0; i < rendererCount; i++) {
TrackGroupArray trackGroups = trackInfo.getTrackGroups(i); TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i);
if (trackGroups.length != 0) { if (trackGroups.length != 0) {
Button button = new Button(this); Button button = new Button(this);
int label; int label;
......
...@@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -51,7 +51,7 @@ import java.util.Locale; ...@@ -51,7 +51,7 @@ import java.util.Locale;
private final MappingTrackSelector selector; private final MappingTrackSelector selector;
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private TrackInfo trackInfo; private MappedTrackInfo trackInfo;
private int rendererIndex; private int rendererIndex;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private boolean[] trackGroupsAdaptive; private boolean[] trackGroupsAdaptive;
...@@ -82,7 +82,7 @@ import java.util.Locale; ...@@ -82,7 +82,7 @@ import java.util.Locale;
* @param trackInfo The current track information. * @param trackInfo The current track information.
* @param rendererIndex The index of the renderer. * @param rendererIndex The index of the renderer.
*/ */
public void showSelectionDialog(Activity activity, CharSequence title, TrackInfo trackInfo, public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo,
int rendererIndex) { int rendererIndex) {
this.trackInfo = trackInfo; this.trackInfo = trackInfo;
this.rendererIndex = rendererIndex; this.rendererIndex = rendererIndex;
...@@ -203,11 +203,7 @@ import java.util.Locale; ...@@ -203,11 +203,7 @@ import java.util.Locale;
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (isDisabled) { selector.setRendererDisabled(rendererIndex, isDisabled);
selector.setRendererDisabled(rendererIndex, true);
return;
}
selector.setRendererDisabled(rendererIndex, false);
if (override != null) { if (override != null) {
selector.setSelectionOverride(rendererIndex, trackGroups, override); selector.setSelectionOverride(rendererIndex, trackGroups, override);
} else { } else {
......
...@@ -100,7 +100,8 @@ public final class CronetDataSourceTest { ...@@ -100,7 +100,8 @@ public final class CronetDataSourceTest {
Executor executor, int priority, Executor executor, int priority,
Collection<Object> connectionAnnotations, Collection<Object> connectionAnnotations,
boolean disableCache, boolean disableCache,
boolean disableConnectionMigration); boolean disableConnectionMigration,
boolean allowDirectExecutor);
} }
@Mock @Mock
...@@ -108,7 +109,7 @@ public final class CronetDataSourceTest { ...@@ -108,7 +109,7 @@ public final class CronetDataSourceTest {
@Mock @Mock
private Predicate<String> mockContentTypePredicate; private Predicate<String> mockContentTypePredicate;
@Mock @Mock
private TransferListener mockTransferListener; private TransferListener<CronetDataSource> mockTransferListener;
@Mock @Mock
private Clock mockClock; private Clock mockClock;
@Mock @Mock
...@@ -143,6 +144,7 @@ public final class CronetDataSourceTest { ...@@ -143,6 +144,7 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest); any(Boolean.class))).thenReturn(mockUrlRequest);
mockStatusResponse(); mockStatusResponse();
...@@ -170,8 +172,8 @@ public final class CronetDataSourceTest { ...@@ -170,8 +172,8 @@ public final class CronetDataSourceTest {
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void testOpeningTwiceThrows() throws HttpDataSourceException, IllegalStateException { public void testOpeningTwiceThrows() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
assertConnectionState(CronetDataSource.IDLE_CONNECTION); assertConnectionState(CronetDataSource.IDLE_CONNECTION);
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
...@@ -181,7 +183,7 @@ public final class CronetDataSourceTest { ...@@ -181,7 +183,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testCallbackFromPreviousRequest() throws HttpDataSourceException { public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
dataSourceUnderTest.close(); dataSourceUnderTest.close();
...@@ -194,6 +196,7 @@ public final class CronetDataSourceTest { ...@@ -194,6 +196,7 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest2); any(Boolean.class))).thenReturn(mockUrlRequest2);
doAnswer(new Answer<Object>() { doAnswer(new Answer<Object>() {
@Override @Override
...@@ -214,7 +217,7 @@ public final class CronetDataSourceTest { ...@@ -214,7 +217,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestStartCalled() throws HttpDataSourceException { public void testRequestStartCalled() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
verify(mockCronetEngine).createRequest( verify(mockCronetEngine).createRequest(
...@@ -224,13 +227,14 @@ public final class CronetDataSourceTest { ...@@ -224,13 +227,14 @@ public final class CronetDataSourceTest {
anyInt(), anyInt(),
eq(Collections.emptyList()), eq(Collections.emptyList()),
any(Boolean.class), any(Boolean.class),
any(Boolean.class),
any(Boolean.class)); any(Boolean.class));
verify(mockUrlRequest).start(); verify(mockUrlRequest).start();
} }
@Test @Test
public void testRequestHeadersSet() throws HttpDataSourceException { public void testRequestHeadersSet() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
testResponseHeader.put("Content-Length", Long.toString(5000L)); testResponseHeader.put("Content-Length", Long.toString(5000L));
...@@ -248,13 +252,29 @@ public final class CronetDataSourceTest { ...@@ -248,13 +252,29 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpen() throws HttpDataSourceException { public void testRequestOpen() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec)); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec));
assertConnectionState(CronetDataSource.OPEN_CONNECTION); assertConnectionState(CronetDataSource.OPEN_CONNECTION);
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
} }
@Test
public void testRequestOpenGzippedCompressedReturnsDataSpecLength()
throws HttpDataSourceException {
testResponseHeader.put("Content-Encoding", "gzip");
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
mockResponseStartSuccess();
// Data spec's requested length, 5000. Test response's length, 16,000.
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
assertEquals(5000 /* contentLength */, dataSourceUnderTest.open(testDataSpec));
assertConnectionState(CronetDataSource.OPEN_CONNECTION);
verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec);
}
@Test @Test
public void testRequestOpenFail() { public void testRequestOpenFail() {
mockResponseStartFailure(); mockResponseStartFailure();
...@@ -291,7 +311,7 @@ public final class CronetDataSourceTest { ...@@ -291,7 +311,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesStatusCode() { public void testRequestOpenValidatesStatusCode() {
mockResponesStartSuccess(); mockResponseStartSuccess();
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
try { try {
...@@ -308,7 +328,7 @@ public final class CronetDataSourceTest { ...@@ -308,7 +328,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesContentTypePredicate() { public void testRequestOpenValidatesContentTypePredicate() {
mockResponesStartSuccess(); mockResponseStartSuccess();
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false);
try { try {
...@@ -325,7 +345,7 @@ public final class CronetDataSourceTest { ...@@ -325,7 +345,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestOpenValidatesContentLength() { public void testRequestOpenValidatesContentLength() {
mockResponesStartSuccess(); mockResponseStartSuccess();
// Data spec's requested length, 5000. Test response's length, 16,000. // Data spec's requested length, 5000. Test response's length, 16,000.
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
...@@ -344,7 +364,7 @@ public final class CronetDataSourceTest { ...@@ -344,7 +364,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpen() throws HttpDataSourceException { public void testPostRequestOpen() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec)); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec));
...@@ -354,7 +374,7 @@ public final class CronetDataSourceTest { ...@@ -354,7 +374,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpenValidatesContentType() { public void testPostRequestOpenValidatesContentType() {
mockResponesStartSuccess(); mockResponseStartSuccess();
try { try {
dataSourceUnderTest.open(testPostDataSpec); dataSourceUnderTest.open(testPostDataSpec);
...@@ -366,7 +386,7 @@ public final class CronetDataSourceTest { ...@@ -366,7 +386,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testPostRequestOpenRejects307Redirects() { public void testPostRequestOpenRejects307Redirects() {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockResponseStartRedirect(); mockResponseStartRedirect();
try { try {
...@@ -380,7 +400,7 @@ public final class CronetDataSourceTest { ...@@ -380,7 +400,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testRequestReadTwice() throws HttpDataSourceException { public void testRequestReadTwice() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
...@@ -402,7 +422,7 @@ public final class CronetDataSourceTest { ...@@ -402,7 +422,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testSecondRequestNoContentLength() throws HttpDataSourceException { public void testSecondRequestNoContentLength() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
byte[] returnedBuffer = new byte[8]; byte[] returnedBuffer = new byte[8];
...@@ -433,7 +453,23 @@ public final class CronetDataSourceTest { ...@@ -433,7 +453,23 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadWithOffset() throws HttpDataSourceException { public void testReadWithOffset() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess();
dataSourceUnderTest.open(testDataSpec);
byte[] returnedBuffer = new byte[16];
int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8);
assertArrayEquals(prefixZeros(buildTestDataArray(0, 8), 16), returnedBuffer);
assertEquals(8, bytesRead);
verify(mockTransferListener).onBytesTransferred(dataSourceUnderTest, 8);
}
@Test
public void testReadWithUnsetLength() throws HttpDataSourceException {
testResponseHeader.remove("Content-Length");
testUrlResponseInfo = createUrlResponseInfo(200); // statusCode
mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
...@@ -447,7 +483,7 @@ public final class CronetDataSourceTest { ...@@ -447,7 +483,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadReturnsWhatItCan() throws HttpDataSourceException { public void testReadReturnsWhatItCan() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
...@@ -461,7 +497,7 @@ public final class CronetDataSourceTest { ...@@ -461,7 +497,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testClosedMeansClosed() throws HttpDataSourceException { public void testClosedMeansClosed() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
int bytesRead = 0; int bytesRead = 0;
...@@ -489,7 +525,7 @@ public final class CronetDataSourceTest { ...@@ -489,7 +525,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testOverread() throws HttpDataSourceException { public void testOverread() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadSuccess(); mockReadSuccess();
// Ask for 16 bytes // Ask for 16 bytes
...@@ -676,7 +712,7 @@ public final class CronetDataSourceTest { ...@@ -676,7 +712,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testExceptionFromTransferListener() throws HttpDataSourceException { public void testExceptionFromTransferListener() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that // Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
// the subsequent open() call succeeds. // the subsequent open() call succeeds.
...@@ -695,7 +731,7 @@ public final class CronetDataSourceTest { ...@@ -695,7 +731,7 @@ public final class CronetDataSourceTest {
@Test @Test
public void testReadFailure() throws HttpDataSourceException { public void testReadFailure() throws HttpDataSourceException {
mockResponesStartSuccess(); mockResponseStartSuccess();
mockReadFailure(); mockReadFailure();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
...@@ -722,7 +758,7 @@ public final class CronetDataSourceTest { ...@@ -722,7 +758,7 @@ public final class CronetDataSourceTest {
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class)); }).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
} }
private void mockResponesStartSuccess() { private void mockResponseStartSuccess() {
doAnswer(new Answer<Object>() { doAnswer(new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
......
...@@ -300,6 +300,10 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -300,6 +300,10 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
try { try {
validateResponse(info); validateResponse(info);
responseInfo = info; responseInfo = info;
if (isCompressed(info)) {
contentLength = currentDataSpec.length;
} else {
// Check content length. // Check content length.
contentLength = getContentLength(info.getAllHeaders()); contentLength = getContentLength(info.getAllHeaders());
// If a specific length is requested and a specific length is returned but the 2 don't match // If a specific length is requested and a specific length is returned but the 2 don't match
...@@ -310,6 +314,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -310,6 +314,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
throw new OpenException("Content length did not match requested length", currentDataSpec, throw new OpenException("Content length did not match requested length", currentDataSpec,
getCurrentRequestStatus()); getCurrentRequestStatus());
} }
}
if (contentLength > 0) { if (contentLength > 0) {
expectedBytesRemainingToRead = new AtomicLong(contentLength); expectedBytesRemainingToRead = new AtomicLong(contentLength);
...@@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
} }
} }
/**
* Returns {@code true} iff the content is compressed.
*
* <p>If {@code true}, clients cannot use the value of content length from the request headers to
* read the data, since Cronet returns the uncompressed data and this content length reflects the
* compressed content length.
*/
private boolean isCompressed(UrlResponseInfo info) {
for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) {
if (entry.getKey().equalsIgnoreCase("Content-Encoding")) {
return !entry.getValue().equalsIgnoreCase("identity");
}
}
return false;
}
private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException { private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException {
// Check for a valid response code. // Check for a valid response code.
int responseCode = info.getHttpStatusCode(); int responseCode = info.getHttpStatusCode();
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler());
player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
ExtractorMediaSource mediaSource = new ExtractorMediaSource( ExtractorMediaSource mediaSource = new ExtractorMediaSource(
......
...@@ -55,6 +55,7 @@ dependencies { ...@@ -55,6 +55,7 @@ dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'org.mockito:mockito-core:1.9.5' androidTestCompile 'org.mockito:mockito-core:1.9.5'
compile 'com.android.support:support-annotations:24.2.0'
} }
android.libraryVariants.all { variant -> android.libraryVariants.all { variant ->
...@@ -95,7 +96,7 @@ publish { ...@@ -95,7 +96,7 @@ publish {
userOrg = 'google' userOrg = 'google'
groupId = 'com.google.android.exoplayer' groupId = 'com.google.android.exoplayer'
artifactId = 'exoplayer' artifactId = 'exoplayer'
version = 'r2.0.0' version = 'r2.0.1'
description = 'The ExoPlayer library.' description = 'The ExoPlayer library.'
website = 'https://github.com/google/ExoPlayer' website = 'https://github.com/google/ExoPlayer'
} }
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true
seekMap:
isSeekable = true
duration = 26125
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/mpeg
maxInputSize = 4096
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 2
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true
...@@ -8,7 +8,7 @@ track 0: ...@@ -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: ...@@ -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 = 37657545511 time = 37657528822
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 3, hash 7724
sample 2: sample 2:
time = 37657578866 time = 37657545511
flags = 1 flags = 1
data = length 2, hash FFFFF6DC data = length 3, hash 766F
sample 3: sample 3:
time = 37657612244 time = 37657562177
flags = 1 flags = 1
data = length 2, hash FFFFF65B data = length 3, hash 7724
sample 4: sample 4:
time = 37657645600 time = 37657578866
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 3, hash 767E
sample 5: sample 5:
time = 37657678977 time = 37657595555
flags = 1 flags = 1
data = length 2, hash FFFFF67B data = length 3, hash 7724
sample 6: sample 6:
time = 37657712333 time = 37657612244
flags = 1 flags = 1
data = length 2, hash 2B5 data = length 15, hash E4359178
sample 7: sample 7:
time = 37657745711 time = 37657628911
flags = 1 flags = 1
data = length 2, hash F5 data = length 3, hash 7724
sample 8: sample 8:
time = 37657779066 time = 37657645600
flags = 1 flags = 1
data = length 2, hash FFFFF87A data = length 12, hash 15EBEB66
sample 9: sample 9:
time = 37657812444 time = 37657662288
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 3, hash 7724
sample 10: sample 10:
time = 37657845800 time = 37657678977
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 3, hash 761D
sample 11: sample 11:
time = 37657879177 time = 37657695644
flags = 1 flags = 1
data = length 2, hash 803 data = length 3, hash 7724
sample 12: sample 12:
time = 37657912533 time = 37657712333
flags = 1 flags = 1
data = length 2, hash 1F8 data = length 30, hash E181418F
sample 13: sample 13:
time = 37657945911 time = 37657729022
flags = 1 flags = 1
data = length 2, hash 117A data = length 6, hash 36289CE2
sample 14: sample 14:
time = 37657979266 time = 37657745711
flags = 1 flags = 1
data = length 2, hash 166 data = length 12, hash 3C304F5B
sample 15: sample 15:
time = 37658012644 time = 37657762377
flags = 1 flags = 1
data = length 2, hash 105A data = length 3, hash 7724
sample 16: sample 16:
time = 37658046000 time = 37657779066
flags = 1 flags = 1
data = length 2, hash FCF data = length 12, hash 88DD8EF6
sample 17: sample 17:
time = 37658079377 time = 37657795755
flags = 1 flags = 1
data = length 2, hash 1253 data = length 3, hash 7724
sample 18: sample 18:
time = 37658112733 time = 37657812444
flags = 1 flags = 1
data = length 2, hash 11DA data = length 12, hash 8B411833
sample 19: sample 19:
time = 37658146111 time = 37657829111
flags = 1 flags = 1
data = length 2, hash 795 data = length 3, hash 7724
sample 20: sample 20:
time = 37658179466 time = 37657845800
flags = 1 flags = 1
data = length 2, hash 103E data = length 12, hash 742A2DF1
sample 21: sample 21:
time = 37658212844 time = 37657862488
flags = 1 flags = 1
data = length 2, hash 120F data = length 3, hash 7724
sample 22: sample 22:
time = 37658246200 time = 37657879177
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 12, hash 9A2ECBEE
sample 23: sample 23:
time = 37658279577 time = 37657895844
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 3, hash 7724
sample 24: sample 24:
time = 37658312933 time = 37657912533
flags = 1 flags = 1
data = length 2, hash FFFFF71B data = length 12, hash 562688EA
sample 25: sample 25:
time = 37658346311 time = 37657929222
flags = 1 flags = 1
data = length 2, hash F91 data = length 3, hash 7724
sample 26: sample 26:
time = 37658379666 time = 37657945911
flags = 1 flags = 1
data = length 2, hash 166 data = length 12, hash ADE4B953
sample 27: sample 27:
time = 37658413044 time = 37657962577
flags = 1 flags = 1
data = length 2, hash 1023 data = length 3, hash 7724
sample 28: sample 28:
time = 37658446400 time = 37657979266
flags = 1 flags = 1
data = length 2, hash 117A data = length 12, hash F927E3E5
sample 29: sample 29:
time = 37658479777 time = 37657995955
flags = 1 flags = 1
data = length 2, hash 784 data = length 3, hash 7724
sample 30: sample 30:
time = 37658513133 time = 37658012644
flags = 1 flags = 1
data = length 2, hash 1F8 data = length 12, hash EA327945
sample 31: sample 31:
time = 37658546511 time = 37658029311
flags = 1 flags = 1
data = length 2, hash 10D9 data = length 3, hash 7724
sample 32: sample 32:
time = 37658579866 time = 37658046000
flags = 1 flags = 1
data = length 2, hash 935 data = length 12, hash 3E5DA13C
sample 33: sample 33:
time = 37658613244 time = 37658062688
flags = 1 flags = 1
data = length 2, hash 2B5 data = length 3, hash 7724
sample 34: sample 34:
time = 37658646600 time = 37658079377
flags = 1 flags = 1
data = length 2, hash F5 data = length 12, hash BF646AE3
sample 35: sample 35:
time = 37658679977 time = 37658096044
flags = 1 flags = 1
data = length 2, hash FFFFF87A data = length 3, hash 7724
sample 36: sample 36:
time = 37658713333 time = 37658112733
flags = 1 flags = 1
data = length 2, hash FFFFF698 data = length 12, hash 41E3BA78
sample 37: sample 37:
time = 37658746711 time = 37658129422
flags = 1 flags = 1
data = length 2, hash 1F4 data = length 3, hash 7724
sample 38: sample 38:
time = 37658780066 time = 37658146111
flags = 1 flags = 1
data = length 2, hash 793 data = length 12, hash A2945EF6
sample 39: sample 39:
time = 37658813444 time = 37658162777
flags = 1 flags = 1
data = length 2, hash FF0 data = length 3, hash 7724
sample 40: sample 40:
time = 37658846800 time = 37658179466
flags = 1 flags = 1
data = length 2, hash 16B data = length 12, hash 26735812
sample 41: sample 41:
time = 37658880177 time = 37658196155
flags = 1 flags = 1
data = length 2, hash 2C0 data = length 3, hash 7724
sample 42: sample 42:
time = 37658913533 time = 37658212844
flags = 1 flags = 1
data = length 2, hash FFFFF953 data = length 12, hash DC14D3D8
sample 43: sample 43:
time = 37658946911 time = 37658229511
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 44: sample 44:
time = 37658980266 time = 37658246200
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 882191BE
sample 45: sample 45:
time = 37659013644 time = 37658262888
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 46: sample 46:
time = 37659047000 time = 37658279577
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 8B4886B1
sample 47: sample 47:
time = 37659080377 time = 37658296244
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 48: sample 48:
time = 37659113733 time = 37658312933
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 98D98F96
sample 49: sample 49:
time = 37659147111 time = 37658329622
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 50: sample 50:
time = 37659180466 time = 37658346311
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 30, hash CF8E53E3
sample 51: sample 51:
time = 37659213844 time = 37658362977
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 6, hash 36289CE2
sample 52: sample 52:
time = 37659247200 time = 37658379666
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash F883C9EE
sample 53: sample 53:
time = 37659280577 time = 37658396355
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 54: sample 54:
time = 37659313933 time = 37658413044
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 6E6B2B9C
sample 55: sample 55:
time = 37659347311 time = 37658429711
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 56: sample 56:
time = 37659380666 time = 37658446400
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash B4FE7F08
sample 57: sample 57:
time = 37659414044 time = 37658463088
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 58: sample 58:
time = 37659447400 time = 37658479777
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 5A1EA7C7
sample 59: sample 59:
time = 37659480777 time = 37658496444
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 60: sample 60:
time = 37659514133 time = 37658513133
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 46BD6CC9
sample 61: sample 61:
time = 37659547511 time = 37658529822
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 3, hash 7724
sample 62: sample 62:
time = 37659580866 time = 37658546511
flags = 1 flags = 1
data = length 2, hash FFFFF3C1 data = length 12, hash 1B1E2554
sample 63: sample 63:
time = 37659614244 time = 37658563177
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 3, hash 7724
sample 64: sample 64:
time = 37659647600 time = 37658579866
flags = 1 flags = 1
data = length 2, hash FFFFF6DC data = length 12, hash 91FCC537
sample 65: sample 65:
time = 37659680977 time = 37658596555
flags = 1 flags = 1
data = length 2, hash FFFFF65B data = length 3, hash 7724
sample 66: sample 66:
time = 37659714333 time = 37658613244
flags = 1 flags = 1
data = length 2, hash FFFFF6CD data = length 12, hash A9355E1B
sample 67: sample 67:
time = 37659747711 time = 37658629911
flags = 1 flags = 1
data = length 2, hash FFFFF6FF data = length 3, hash 7724
sample 68: sample 68:
time = 37659781066 time = 37658646600
flags = 1 flags = 1
data = length 2, hash FFFFF6AC data = length 12, hash 2511F69B
sample 69: sample 69:
time = 37659814444 time = 37658663288
flags = 1 flags = 1
data = length 2, hash FFFFF5FE data = length 3, hash 7724
sample 70: sample 70:
time = 37659847800 time = 37658679977
flags = 1 flags = 1
data = length 2, hash FFFFFEF7 data = length 12, hash 90925736
sample 71: sample 71:
time = 37659881177 time = 37658696644
flags = 1 flags = 1
data = length 2, hash 120C data = length 3, hash 7724
sample 72: sample 72:
time = 37659914533 time = 37658713333
flags = 1 flags = 1
data = length 2, hash 1124 data = length 21, hash 431EEE30
sample 73: sample 73:
time = 37659947911 time = 37658730022
flags = 1 flags = 1
data = length 2, hash 1A9 data = length 3, hash 7724
sample 74: sample 74:
time = 37658746711
flags = 1
data = length 12, hash 7BDEF631
sample 75:
time = 37658763377
flags = 1
data = length 3, hash 7724
sample 76:
time = 37658780066
flags = 1
data = length 12, hash A2EEF59E
sample 77:
time = 37658796755
flags = 1
data = length 3, hash 7724
sample 78:
time = 37658813444
flags = 1
data = length 12, hash BFC6C022
sample 79:
time = 37658830111
flags = 1
data = length 3, hash 7724
sample 80:
time = 37658846800
flags = 1
data = length 12, hash CD4D8FCA
sample 81:
time = 37658863488
flags = 1
data = length 3, hash 7724
sample 82:
time = 37658880177
flags = 1
data = length 12, hash 2BDE8EFA
sample 83:
time = 37658896844
flags = 1
data = length 3, hash 7724
sample 84:
time = 37658913533
flags = 1
data = length 12, hash 8C858812
sample 85:
time = 37658930222
flags = 1
data = length 3, hash 7724
sample 86:
time = 37658946911
flags = 1
data = length 12, hash DE7D0E31
sample 87:
time = 37658963577
flags = 1
data = length 3, hash 7724
sample 88:
time = 37658980266
flags = 1
data = length 3, hash 7363
sample 89:
time = 37658996955
flags = 1
data = length 3, hash 7724
sample 90:
time = 37659013644
flags = 1
data = length 3, hash 7363
sample 91:
time = 37659030311
flags = 1
data = length 3, hash 7724
sample 92:
time = 37659047000
flags = 1
data = length 3, hash 7363
sample 93:
time = 37659063688
flags = 1
data = length 3, hash 7724
sample 94:
time = 37659080377
flags = 1
data = length 3, hash 7363
sample 95:
time = 37659097044
flags = 1
data = length 3, hash 7724
sample 96:
time = 37659113733
flags = 1
data = length 3, hash 7363
sample 97:
time = 37659130422
flags = 1
data = length 3, hash 7724
sample 98:
time = 37659147111
flags = 1
data = length 3, hash 7363
sample 99:
time = 37659163777
flags = 1
data = length 3, hash 7724
sample 100:
time = 37659180466
flags = 1
data = length 3, hash 7363
sample 101:
time = 37659197155
flags = 1
data = length 3, hash 7724
sample 102:
time = 37659213844
flags = 1
data = length 3, hash 7363
sample 103:
time = 37659230511
flags = 1
data = length 3, hash 7724
sample 104:
time = 37659247200
flags = 1
data = length 3, hash 7363
sample 105:
time = 37659263888
flags = 1
data = length 3, hash 7724
sample 106:
time = 37659280577
flags = 1
data = length 3, hash 7363
sample 107:
time = 37659297244
flags = 1
data = length 3, hash 7724
sample 108:
time = 37659313933
flags = 1
data = length 3, hash 7363
sample 109:
time = 37659330622
flags = 1
data = length 3, hash 7724
sample 110:
time = 37659347311
flags = 1
data = length 3, hash 7363
sample 111:
time = 37659363977
flags = 1
data = length 3, hash 7724
sample 112:
time = 37659380666
flags = 1
data = length 3, hash 7363
sample 113:
time = 37659397355
flags = 1
data = length 3, hash 7724
sample 114:
time = 37659414044
flags = 1
data = length 3, hash 7363
sample 115:
time = 37659430711
flags = 1
data = length 3, hash 7724
sample 116:
time = 37659447400
flags = 1
data = length 3, hash 7363
sample 117:
time = 37659464088
flags = 1
data = length 3, hash 7724
sample 118:
time = 37659480777
flags = 1
data = length 3, hash 7363
sample 119:
time = 37659497444
flags = 1
data = length 3, hash 7724
sample 120:
time = 37659514133
flags = 1
data = length 3, hash 7363
sample 121:
time = 37659530822
flags = 1
data = length 3, hash 7724
sample 122:
time = 37659547511
flags = 1
data = length 3, hash 7363
sample 123:
time = 37659564177
flags = 1
data = length 3, hash 7724
sample 124:
time = 37659580866
flags = 1
data = length 3, hash 7363
sample 125:
time = 37659597555
flags = 1
data = length 3, hash 7724
sample 126:
time = 37659614244
flags = 1
data = length 3, hash 766F
sample 127:
time = 37659630911
flags = 1
data = length 3, hash 7724
sample 128:
time = 37659647600
flags = 1
data = length 3, hash 767E
sample 129:
time = 37659664288
flags = 1
data = length 3, hash 7724
sample 130:
time = 37659680977
flags = 1
data = length 15, hash 191B585A
sample 131:
time = 37659697644
flags = 1
data = length 3, hash 7724
sample 132:
time = 37659714333
flags = 1
data = length 12, hash 15EC5FC5
sample 133:
time = 37659731022
flags = 1
data = length 3, hash 7724
sample 134:
time = 37659747711
flags = 1
data = length 3, hash 76A1
sample 135:
time = 37659764377
flags = 1
data = length 3, hash 7724
sample 136:
time = 37659781066
flags = 1
data = length 30, hash E8012479
sample 137:
time = 37659797755
flags = 1
data = length 6, hash 36289D5E
sample 138:
time = 37659814444
flags = 1
data = length 12, hash D32F29F3
sample 139:
time = 37659831111
flags = 1
data = length 3, hash 7724
sample 140:
time = 37659847800
flags = 1
data = length 21, hash 6258623
sample 141:
time = 37659864488
flags = 1
data = length 3, hash 7724
sample 142:
time = 37659881177
flags = 1
data = length 12, hash FE69ABA2
sample 143:
time = 37659897844
flags = 1
data = length 3, hash 7724
sample 144:
time = 37659914533
flags = 1
data = length 12, hash 958D0815
sample 145:
time = 37659931222
flags = 1
data = length 3, hash 7724
sample 146:
time = 37659947911
flags = 1
data = length 12, hash FF57BFD8
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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
}, "mp3/bear.mp3", getInstrumentation()); }, "mp3/bear.mp3", getInstrumentation());
} }
public void testTrimmedMp3Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new Mp3Extractor();
}
}, "mp3/play-trimmed.mp3", getInstrumentation());
}
} }
...@@ -16,8 +16,16 @@ ...@@ -16,8 +16,16 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Random; import java.util.Random;
...@@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase {
}, "ts/sample.ts", fileData, getInstrumentation()); }, "ts/sample.ts", fileData, getInstrumentation());
} }
public void testCustomPesReader() throws Exception {
CustomEsReaderFactory factory = new CustomEsReaderFactory();
TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory);
FakeExtractorInput input = new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
.setSimulateIOErrors(false)
.setSimulateUnknownLength(false)
.setSimulatePartialReads(false).build();
FakeExtractorOutput output = new FakeExtractorOutput();
tsExtractor.init(output);
tsExtractor.seek(input.getPosition());
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) {
readResult = tsExtractor.read(input, seekPositionHolder);
}
CustomEsReader reader = factory.reader;
assertEquals(2, reader.packetsRead);
TrackOutput trackOutput = reader.getTrackOutput();
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
assertEquals(
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
((FakeTrackOutput) trackOutput).format);
}
private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException { private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (((byte) i) == TS_SYNC_BYTE) { if (((byte) i) == TS_SYNC_BYTE) {
...@@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase {
} }
} }
private static final class CustomEsReader extends ElementaryStreamReader {
public int packetsRead = 0;
public CustomEsReader(TrackOutput output, String language) {
super(output);
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
language, null, 0));
}
@Override
public void seek() {
}
@Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
}
@Override
public void consume(ParsableByteArray data) {
}
@Override
public void packetFinished() {
packetsRead++;
}
public TrackOutput getTrackOutput() {
return output;
}
}
private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory {
private final ElementaryStreamReader.Factory defaultFactory;
private CustomEsReader reader;
public CustomEsReaderFactory() {
defaultFactory = new DefaultStreamReaderFactory();
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if (streamType == 3) {
// We need to manually avoid a duplicate custom reader creation.
if (reader == null) {
reader = new CustomEsReader(output.track(pid), esInfo.language);
}
return reader;
} else {
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output);
}
}
}
} }
...@@ -17,8 +17,11 @@ package com.google.android.exoplayer2; ...@@ -17,8 +17,11 @@ package com.google.android.exoplayer2;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.support.annotation.IntDef;
import android.view.Surface; import android.view.Surface;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID; import java.util.UUID;
/** /**
...@@ -71,54 +74,78 @@ public final class C { ...@@ -71,54 +74,78 @@ public final class C {
public static final String UTF8_NAME = "UTF-8"; public static final String UTF8_NAME = "UTF-8";
/** /**
* Crypto modes for a codec.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
public @interface CryptoMode {}
/**
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
/**
* @see MediaCodec#CRYPTO_MODE_AES_CTR * @see MediaCodec#CRYPTO_MODE_AES_CTR
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
/**
* @see MediaCodec#CRYPTO_MODE_AES_CBC
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
/**
* Represents an audio encoding, or an invalid or unset value.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS,
ENCODING_DTS_HD})
public @interface Encoding {}
/** /**
* Represents a PCM audio encoding, or an invalid or unset value.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT})
public @interface PcmEncoding {}
/**
* @see AudioFormat#ENCODING_INVALID * @see AudioFormat#ENCODING_INVALID
*/ */
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/** /**
* @see AudioFormat#ENCODING_PCM_8BIT * @see AudioFormat#ENCODING_PCM_8BIT
*/ */
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/** /**
* @see AudioFormat#ENCODING_PCM_16BIT * @see AudioFormat#ENCODING_PCM_16BIT
*/ */
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/** /**
* PCM encoding with 24 bits per sample. * PCM encoding with 24 bits per sample.
*/ */
public static final int ENCODING_PCM_24BIT = 0x80000000; public static final int ENCODING_PCM_24BIT = 0x80000000;
/** /**
* PCM encoding with 32 bits per sample. * PCM encoding with 32 bits per sample.
*/ */
public static final int ENCODING_PCM_32BIT = 0x40000000; public static final int ENCODING_PCM_32BIT = 0x40000000;
/** /**
* @see AudioFormat#ENCODING_AC3 * @see AudioFormat#ENCODING_AC3
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/** /**
* @see AudioFormat#ENCODING_E_AC3 * @see AudioFormat#ENCODING_E_AC3
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
/** /**
* @see AudioFormat#ENCODING_DTS * @see AudioFormat#ENCODING_DTS
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
/** /**
* @see AudioFormat#ENCODING_DTS_HD * @see AudioFormat#ENCODING_DTS_HD
*/ */
...@@ -133,47 +160,92 @@ public final class C { ...@@ -133,47 +160,92 @@ public final class C {
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
/** /**
* Flags which can apply to a buffer containing a media sample.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY})
public @interface BufferFlags {}
/**
* Indicates that a buffer holds a synchronization sample. * Indicates that a buffer holds a synchronization sample.
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
/** /**
* Flag for empty buffers that signal that the end of the stream was reached. * Flag for empty buffers that signal that the end of the stream was reached.
*/ */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/** /**
* Indicates that a buffer is (at least partially) encrypted. * Indicates that a buffer is (at least partially) encrypted.
*/ */
public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000; public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000;
/** /**
* Indicates that a buffer should be decoded but not rendered. * Indicates that a buffer should be decoded but not rendered.
*/ */
public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000; public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000;
/** /**
* Track selection flags.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED,
SELECTION_FLAG_AUTOSELECT})
public @interface SelectionFlags {}
/**
* Indicates that the track should be selected if user preferences do not state otherwise.
*/
public static final int SELECTION_FLAG_DEFAULT = 1;
/**
* Indicates that the track must be displayed. Only applies to text tracks.
*/
public static final int SELECTION_FLAG_FORCED = 2;
/**
* Indicates that the player may choose to play the track in absence of an explicit user
* preference.
*/
public static final int SELECTION_FLAG_AUTOSELECT = 4;
/**
* Represents a streaming or other media type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
public @interface ContentType {}
/**
* Value returned by {@link Util#inferContentType(String)} for DASH manifests.
*/
public static final int TYPE_DASH = 0;
/**
* Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests.
*/
public static final int TYPE_SS = 1;
/**
* Value returned by {@link Util#inferContentType(String)} for HLS manifests.
*/
public static final int TYPE_HLS = 2;
/**
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
* Smooth Streaming manifests.
*/
public static final int TYPE_OTHER = 3;
/**
* A return value for methods where the end of an input was encountered. * A return value for methods where the end of an input was encountered.
*/ */
public static final int RESULT_END_OF_INPUT = -1; public static final int RESULT_END_OF_INPUT = -1;
/** /**
* A return value for methods where the length of parsed data exceeds the maximum length allowed. * A return value for methods where the length of parsed data exceeds the maximum length allowed.
*/ */
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2; public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
/** /**
* A return value for methods where nothing was read. * A return value for methods where nothing was read.
*/ */
public static final int RESULT_NOTHING_READ = -3; public static final int RESULT_NOTHING_READ = -3;
/** /**
* A return value for methods where a buffer was read. * A return value for methods where a buffer was read.
*/ */
public static final int RESULT_BUFFER_READ = -4; public static final int RESULT_BUFFER_READ = -4;
/** /**
* A return value for methods where a format was read. * A return value for methods where a format was read.
*/ */
...@@ -183,32 +255,26 @@ public final class C { ...@@ -183,32 +255,26 @@ public final class C {
* A data type constant for data of unknown or unspecified type. * A data type constant for data of unknown or unspecified type.
*/ */
public static final int DATA_TYPE_UNKNOWN = 0; public static final int DATA_TYPE_UNKNOWN = 0;
/** /**
* A data type constant for media, typically containing media samples. * A data type constant for media, typically containing media samples.
*/ */
public static final int DATA_TYPE_MEDIA = 1; public static final int DATA_TYPE_MEDIA = 1;
/** /**
* A data type constant for media, typically containing only initialization data. * A data type constant for media, typically containing only initialization data.
*/ */
public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2; public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2;
/** /**
* A data type constant for drm or encryption data. * A data type constant for drm or encryption data.
*/ */
public static final int DATA_TYPE_DRM = 3; public static final int DATA_TYPE_DRM = 3;
/** /**
* A data type constant for a manifest file. * A data type constant for a manifest file.
*/ */
public static final int DATA_TYPE_MANIFEST = 4; public static final int DATA_TYPE_MANIFEST = 4;
/** /**
* A data type constant for time synchronization data. * A data type constant for time synchronization data.
*/ */
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5;
/** /**
* Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
* equal to this value. * equal to this value.
...@@ -219,32 +285,26 @@ public final class C { ...@@ -219,32 +285,26 @@ public final class C {
* A type constant for tracks of unknown type. * A type constant for tracks of unknown type.
*/ */
public static final int TRACK_TYPE_UNKNOWN = -1; public static final int TRACK_TYPE_UNKNOWN = -1;
/** /**
* A type constant for tracks of some default type, where the type itself is unknown. * A type constant for tracks of some default type, where the type itself is unknown.
*/ */
public static final int TRACK_TYPE_DEFAULT = 0; public static final int TRACK_TYPE_DEFAULT = 0;
/** /**
* A type constant for audio tracks. * A type constant for audio tracks.
*/ */
public static final int TRACK_TYPE_AUDIO = 1; public static final int TRACK_TYPE_AUDIO = 1;
/** /**
* A type constant for video tracks. * A type constant for video tracks.
*/ */
public static final int TRACK_TYPE_VIDEO = 2; public static final int TRACK_TYPE_VIDEO = 2;
/** /**
* A type constant for text tracks. * A type constant for text tracks.
*/ */
public static final int TRACK_TYPE_TEXT = 3; public static final int TRACK_TYPE_TEXT = 3;
/** /**
* A type constant for metadata tracks. * A type constant for metadata tracks.
*/ */
public static final int TRACK_TYPE_METADATA = 4; public static final int TRACK_TYPE_METADATA = 4;
/** /**
* Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or
* equal to this value. * equal to this value.
...@@ -255,27 +315,22 @@ public final class C { ...@@ -255,27 +315,22 @@ public final class C {
* A selection reason constant for selections whose reasons are unknown or unspecified. * A selection reason constant for selections whose reasons are unknown or unspecified.
*/ */
public static final int SELECTION_REASON_UNKNOWN = 0; public static final int SELECTION_REASON_UNKNOWN = 0;
/** /**
* A selection reason constant for an initial track selection. * A selection reason constant for an initial track selection.
*/ */
public static final int SELECTION_REASON_INITIAL = 1; public static final int SELECTION_REASON_INITIAL = 1;
/** /**
* A selection reason constant for an manual (i.e. user initiated) track selection. * A selection reason constant for an manual (i.e. user initiated) track selection.
*/ */
public static final int SELECTION_REASON_MANUAL = 2; public static final int SELECTION_REASON_MANUAL = 2;
/** /**
* A selection reason constant for an adaptive track selection. * A selection reason constant for an adaptive track selection.
*/ */
public static final int SELECTION_REASON_ADAPTIVE = 3; public static final int SELECTION_REASON_ADAPTIVE = 3;
/** /**
* A selection reason constant for a trick play track selection. * A selection reason constant for a trick play track selection.
*/ */
public static final int SELECTION_REASON_TRICK_PLAY = 4; public static final int SELECTION_REASON_TRICK_PLAY = 4;
/** /**
* Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than * Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than
* or equal to this value. * or equal to this value.
...@@ -364,15 +419,19 @@ public final class C { ...@@ -364,15 +419,19 @@ public final class C {
public static final int MSG_CUSTOM_BASE = 10000; public static final int MSG_CUSTOM_BASE = 10000;
/** /**
* The stereo mode for 360/3D/VR videos.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
public @interface StereoMode {}
/**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos. * Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
*/ */
public static final int STEREO_MODE_MONO = 0; public static final int STEREO_MODE_MONO = 0;
/** /**
* Indicates Top-Bottom stereo layout, used with 360/3D/VR videos. * Indicates Top-Bottom stereo layout, used with 360/3D/VR videos.
*/ */
public static final int STEREO_MODE_TOP_BOTTOM = 1; public static final int STEREO_MODE_TOP_BOTTOM = 1;
/** /**
* Indicates Left-Right stereo layout, used with 360/3D/VR videos. * Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/ */
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl { ...@@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl {
@Override @Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) { TrackSelections<?> trackSelections) {
targetBufferSize = 0; targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (trackSelections.get(i) != null) { if (trackSelections.get(i) != null) {
......
...@@ -15,9 +15,12 @@ ...@@ -15,9 +15,12 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Thrown when a non-recoverable playback failure occurs. * Thrown when a non-recoverable playback failure occurs.
...@@ -25,6 +28,12 @@ import java.io.IOException; ...@@ -25,6 +28,12 @@ import java.io.IOException;
public final class ExoPlaybackException extends Exception { public final class ExoPlaybackException extends Exception {
/** /**
* The type of source that produced the error.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})
public @interface Type {}
/**
* The error occurred loading data from a {@link MediaSource}. * The error occurred loading data from a {@link MediaSource}.
* <p> * <p>
* Call {@link #getSourceException()} to retrieve the underlying cause. * Call {@link #getSourceException()} to retrieve the underlying cause.
...@@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception {
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
* {@link #TYPE_UNEXPECTED}. * {@link #TYPE_UNEXPECTED}.
*/ */
@Type
public final int type; public final int type;
/** /**
...@@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception { ...@@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception {
return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET); return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET);
} }
private ExoPlaybackException(int type, String message, Throwable cause, int rendererIndex) { private ExoPlaybackException(@Type int type, String message, Throwable cause,
int rendererIndex) {
super(message, cause); super(message, cause);
this.type = type; this.type = type;
this.rendererIndex = rendererIndex; this.rendererIndex = rendererIndex;
......
...@@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline == null || pendingSeekAcks > 0) { if (timeline == null || pendingSeekAcks > 0) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} else { } else {
int periodIndex = playbackInfo.periodIndex; timeline.getPeriod(playbackInfo.periodIndex, period);
timeline.getPeriod(periodIndex, period); return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
int windowIndex = period.windowIndex;
timeline.getWindow(windowIndex, window);
if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
&& window.getPositionInFirstPeriodUs() == 0
&& window.getDurationUs() == period.getDurationUs()) {
return C.usToMs(playbackInfo.bufferedPositionUs);
}
return getCurrentPosition();
} }
} }
......
...@@ -27,7 +27,7 @@ import com.google.android.exoplayer2.source.MediaPeriod; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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,13 +458,12 @@ import java.io.IOException; ...@@ -458,13 +458,12 @@ 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) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
...@@ -530,17 +529,17 @@ import java.io.IOException; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 { ...@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * The version of the library, expressed as a string.
*/ */
String VERSION = "2.0.0"; String VERSION = "2.0.1";
/** /**
* The version of the library, expressed as an integer. * The version of the library, expressed as an integer.
...@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo { ...@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
int VERSION_INT = 2000000; int VERSION_INT = 2000001;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
...@@ -40,20 +40,6 @@ public final class Format implements Parcelable { ...@@ -40,20 +40,6 @@ public final class Format implements Parcelable {
public static final int NO_VALUE = -1; public static final int NO_VALUE = -1;
/** /**
* Indicates that the track should be selected if user preferences do not state otherwise.
*/
public static final int SELECTION_FLAG_DEFAULT = 1;
/**
* Indicates that the track must be displayed. Only applies to text tracks.
*/
public static final int SELECTION_FLAG_FORCED = 2;
/**
* Indicates that the player may choose to play the track in absence of an explicit user
* preference.
*/
public static final int SELECTION_FLAG_AUTOSELECT = 4;
/**
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples. * the timestamps of their parent samples.
*/ */
...@@ -131,6 +117,7 @@ public final class Format implements Parcelable { ...@@ -131,6 +117,7 @@ public final class Format implements Parcelable {
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}. * C#STEREO_MODE_LEFT_RIGHT}.
*/ */
@C.StereoMode
public final int stereoMode; public final int stereoMode;
/** /**
* The projection data for 360/VR video, or null if not applicable. * The projection data for 360/VR video, or null if not applicable.
...@@ -153,6 +140,7 @@ public final class Format implements Parcelable { ...@@ -153,6 +140,7 @@ public final class Format implements Parcelable {
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for * {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for
* other media types. * other media types.
*/ */
@C.PcmEncoding
public final int pcmEncoding; public final int pcmEncoding;
/** /**
* The number of samples to trim from the start of the decoded audio stream. * The number of samples to trim from the start of the decoded audio stream.
...@@ -177,6 +165,7 @@ public final class Format implements Parcelable { ...@@ -177,6 +165,7 @@ public final class Format implements Parcelable {
/** /**
* Track selection flags. * Track selection flags.
*/ */
@C.SelectionFlags
public final int selectionFlags; public final int selectionFlags;
/** /**
...@@ -218,7 +207,7 @@ public final class Format implements Parcelable { ...@@ -218,7 +207,7 @@ public final class Format implements Parcelable {
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int bitrate, int maxInputSize, int width, int height, float frameRate,
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio, List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
byte[] projectionData, int stereoMode, DrmInitData drmInitData) { byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData,
...@@ -229,7 +218,7 @@ public final class Format implements Parcelable { ...@@ -229,7 +218,7 @@ public final class Format implements Parcelable {
public static Format createAudioContainerFormat(String id, String containerMimeType, public static Format createAudioContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, int selectionFlags, String language) { List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData, NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData,
...@@ -238,25 +227,26 @@ public final class Format implements Parcelable { ...@@ -238,25 +227,26 @@ public final class Format implements Parcelable {
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int bitrate, int maxInputSize, int channelCount, int sampleRate,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags, List<byte[]> initializationData, DrmInitData drmInitData,
String language) { @C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language); sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, int bitrate, int maxInputSize, int channelCount, int sampleRate,
List<byte[]> initializationData, DrmInitData drmInitData, int selectionFlags, @C.PcmEncoding int pcmEncoding, List<byte[]> initializationData, DrmInitData drmInitData,
String language) { @C.SelectionFlags int selectionFlags, String language) {
return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount,
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData,
selectionFlags, language); selectionFlags, language);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, int bitrate, int maxInputSize, int channelCount, int sampleRate,
int encoderDelay, int encoderPadding, List<byte[]> initializationData, @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding,
DrmInitData drmInitData, int selectionFlags, String language) { List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE,
...@@ -266,20 +256,21 @@ public final class Format implements Parcelable { ...@@ -266,20 +256,21 @@ public final class Format implements Parcelable {
// Text. // Text.
public static Format createTextContainerFormat(String id, String containerMimeType, public static Format createTextContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int selectionFlags, String language) { String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null); NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null);
} }
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int selectionFlags, String language, DrmInitData drmInitData) { int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
drmInitData, OFFSET_SAMPLE_RELATIVE); drmInitData, OFFSET_SAMPLE_RELATIVE);
} }
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int selectionFlags, String language, DrmInitData drmInitData, int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
long subsampleOffsetUs) { long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
...@@ -313,10 +304,10 @@ public final class Format implements Parcelable { ...@@ -313,10 +304,10 @@ public final class Format implements Parcelable {
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount, float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
String language, long subsampleOffsetUs, List<byte[]> initializationData, int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
DrmInitData drmInitData) { long subsampleOffsetUs, List<byte[]> initializationData, DrmInitData drmInitData) {
this.id = id; this.id = id;
this.containerMimeType = containerMimeType; this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType; this.sampleMimeType = sampleMimeType;
...@@ -343,6 +334,7 @@ public final class Format implements Parcelable { ...@@ -343,6 +334,7 @@ public final class Format implements Parcelable {
this.drmInitData = drmInitData; this.drmInitData = drmInitData;
} }
@SuppressWarnings("ResourceType")
/* package */ Format(Parcel in) { /* package */ Format(Parcel in) {
id = in.readString(); id = in.readString();
containerMimeType = in.readString(); containerMimeType = in.readString();
...@@ -388,8 +380,8 @@ public final class Format implements Parcelable { ...@@ -388,8 +380,8 @@ public final class Format implements Parcelable {
selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData); selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData);
} }
public Format copyWithContainerInfo(String id, int bitrate, int width, int height, public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
int selectionFlags, String language) { @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
...@@ -402,7 +394,7 @@ public final class Format implements Parcelable { ...@@ -402,7 +394,7 @@ public final class Format implements Parcelable {
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language; String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null) DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; || this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
......
...@@ -17,8 +17,7 @@ package com.google.android.exoplayer2; ...@@ -17,8 +17,7 @@ package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
/** /**
...@@ -31,10 +30,10 @@ public interface LoadControl { ...@@ -31,10 +30,10 @@ public interface LoadControl {
* *
* @param renderers The renderers. * @param renderers The renderers.
* @param trackGroups The {@link TrackGroup}s from which the selection was made. * @param trackGroups The {@link TrackGroup}s from which the selection was made.
* @param trackSelections The {@link TrackSelection}s that were made. * @param trackSelections The track selections that were made.
*/ */
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections); TrackSelections<?> trackSelections);
/** /**
* Called by the player when all tracks are disabled. * Called by the player when all tracks are disabled.
......
...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack; ...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
...@@ -40,6 +41,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Frame; ...@@ -40,6 +41,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Frame;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.text.TextRenderer;
import com.google.android.exoplayer2.trackselection.TrackSelections;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
...@@ -80,18 +82,14 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -80,18 +82,14 @@ public final class SimpleExoPlayer implements ExoPlayer {
/** /**
* Called when a frame is rendered for the first time since setting the surface, and when a * Called when a frame is rendered for the first time since setting the surface, and when a
* frame is rendered for the first time since the renderer was reset. * frame is rendered for the first time since a video track was selected.
*
* @param surface The {@link Surface} to which a first frame has been rendered.
*/ */
void onRenderedFirstFrame(Surface surface); void onRenderedFirstFrame();
/** /**
* Called when the renderer is disabled. * Called when a video track is no longer selected.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/ */
void onVideoDisabled(DecoderCounters counters); void onVideoTracksDisabled();
} }
...@@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
private final int videoRendererCount; private final int videoRendererCount;
private final int audioRendererCount; private final int audioRendererCount;
private boolean videoTracksEnabled;
private Format videoFormat; private Format videoFormat;
private Format audioFormat; private Format audioFormat;
private Surface surface;
private SurfaceHolder surfaceHolder; private SurfaceHolder surfaceHolder;
private TextureView textureView; private TextureView textureView;
private TextRenderer.Output textOutput; private TextRenderer.Output textOutput;
...@@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
private float volume; private float volume;
private PlaybackParamsHolder playbackParamsHolder; private PlaybackParamsHolder playbackParamsHolder;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, /* package */ SimpleExoPlayer(Context context, TrackSelector<?> trackSelector,
LoadControl loadControl, DrmSessionManager drmSessionManager, LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler(); mainHandler = new Handler();
componentListener = new ComponentListener(); componentListener = new ComponentListener();
trackSelector.addListener(componentListener);
// Build the renderers. // Build the renderers.
ArrayList<Renderer> renderersList = new ArrayList<>(); ArrayList<Renderer> renderersList = new ArrayList<>();
...@@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
// Internal methods. // Internal methods.
private void buildRenderers(Context context, DrmSessionManager drmSessionManager, private void buildRenderers(Context context,
ArrayList<Renderer> renderersList, long allowedVideoJoiningTimeMs) { DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, ArrayList<Renderer> renderersList,
long allowedVideoJoiningTimeMs) {
MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context,
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener, allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener,
...@@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
private void setVideoSurfaceInternal(Surface surface) { private void setVideoSurfaceInternal(Surface surface) {
this.surface = surface;
ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount];
int count = 0; int count = 0;
for (Renderer renderer : renderers) { for (Renderer renderer : renderers) {
...@@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
private final class ComponentListener implements VideoRendererEventListener, private final class ComponentListener implements VideoRendererEventListener,
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>,
SurfaceHolder.Callback, TextureView.SurfaceTextureListener { SurfaceHolder.Callback, TextureView.SurfaceTextureListener,
TrackSelector.EventListener<Object> {
// VideoRendererEventListener implementation // VideoRendererEventListener implementation
...@@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onRenderedFirstFrame(Surface surface) { public void onRenderedFirstFrame(Surface surface) {
if (videoListener != null) { if (videoListener != null && SimpleExoPlayer.this.surface == surface) {
videoListener.onRenderedFirstFrame(surface); videoListener.onRenderedFirstFrame();
} }
if (videoDebugListener != null) { if (videoDebugListener != null) {
videoDebugListener.onRenderedFirstFrame(surface); videoDebugListener.onRenderedFirstFrame(surface);
...@@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onVideoDisabled(DecoderCounters counters) { public void onVideoDisabled(DecoderCounters counters) {
if (videoListener != null) {
videoListener.onVideoDisabled(counters);
}
if (videoDebugListener != null) { if (videoDebugListener != null) {
videoDebugListener.onVideoDisabled(counters); videoDebugListener.onVideoDisabled(counters);
} }
...@@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer {
// Do nothing. // Do nothing.
} }
// TrackSelector.EventListener implementation
@Override
public void onTrackSelectionsChanged(TrackSelections<?> trackSelections) {
boolean videoTracksEnabled = false;
for (int i = 0; i < renderers.length; i++) {
if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelections.get(i) != null) {
videoTracksEnabled = true;
break;
}
}
if (videoListener != null && SimpleExoPlayer.this.videoTracksEnabled && !videoTracksEnabled) {
videoListener.onVideoTracksDisabled();
}
SimpleExoPlayer.this.videoTracksEnabled = videoTracksEnabled;
}
} }
@TargetApi(23) @TargetApi(23)
......
...@@ -208,7 +208,9 @@ public final class AudioTrack { ...@@ -208,7 +208,9 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack; private android.media.AudioTrack audioTrack;
private int sampleRate; private int sampleRate;
private int channelConfig; private int channelConfig;
@C.Encoding
private int sourceEncoding; private int sourceEncoding;
@C.Encoding
private int targetEncoding; private int targetEncoding;
private boolean passthrough; private boolean passthrough;
private int pcmFrameSize; private int pcmFrameSize;
...@@ -348,8 +350,8 @@ public final class AudioTrack { ...@@ -348,8 +350,8 @@ public final class AudioTrack {
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically. * suitable buffer size automatically.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding, public void configure(String mimeType, int channelCount, int sampleRate,
int specifiedBufferSize) { @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) {
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
case 1: case 1:
...@@ -381,7 +383,7 @@ public final class AudioTrack { ...@@ -381,7 +383,7 @@ public final class AudioTrack {
} }
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
int sourceEncoding; @C.Encoding int sourceEncoding;
if (passthrough) { if (passthrough) {
sourceEncoding = getEncodingForMimeType(mimeType); sourceEncoding = getEncodingForMimeType(mimeType);
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT } else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
...@@ -470,7 +472,7 @@ public final class AudioTrack { ...@@ -470,7 +472,7 @@ public final class AudioTrack {
if (keepSessionIdAudioTrack == null) { if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO; int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
int encoding = C.ENCODING_PCM_16BIT; @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
...@@ -962,7 +964,7 @@ public final class AudioTrack { ...@@ -962,7 +964,7 @@ public final class AudioTrack {
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the * @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
* capacity was insufficient for the output. * capacity was insufficient for the output.
*/ */
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int sourceEncoding, private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding,
ByteBuffer out) { ByteBuffer out) {
int offset = buffer.position(); int offset = buffer.position();
int limit = buffer.limit(); int limit = buffer.limit();
...@@ -1023,6 +1025,7 @@ public final class AudioTrack { ...@@ -1023,6 +1025,7 @@ public final class AudioTrack {
return resampledBuffer; return resampledBuffer;
} }
@C.Encoding
private static int getEncodingForMimeType(String mimeType) { private static int getEncodingForMimeType(String mimeType) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AC3: case MimeTypes.AUDIO_AC3:
...@@ -1038,7 +1041,7 @@ public final class AudioTrack { ...@@ -1038,7 +1041,7 @@ public final class AudioTrack {
} }
} }
private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) { private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) {
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) { if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
return DtsUtil.parseDtsAudioSampleCount(buffer); return DtsUtil.parseDtsAudioSampleCount(buffer);
} else if (encoding == C.ENCODING_AC3) { } else if (encoding == C.ENCODING_AC3) {
......
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; ...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
*/ */
public abstract class Buffer { public abstract class Buffer {
@C.BufferFlags
private int flags; private int flags;
/** /**
...@@ -58,7 +59,7 @@ public abstract class Buffer { ...@@ -58,7 +59,7 @@ public abstract class Buffer {
* @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*} * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
* constants. * constants.
*/ */
public final void setFlags(int flags) { public final void setFlags(@C.BufferFlags int flags) {
this.flags = flags; this.flags = flags;
} }
...@@ -68,7 +69,7 @@ public abstract class Buffer { ...@@ -68,7 +69,7 @@ public abstract class Buffer {
* @param flag The flag to add to this buffer's flags, which should be one of the * @param flag The flag to add to this buffer's flags, which should be one of the
* {@code C.BUFFER_FLAG_*} constants. * {@code C.BUFFER_FLAG_*} constants.
*/ */
public final void addFlag(int flag) { public final void addFlag(@C.BufferFlags int flag) {
flags |= flag; flags |= flag;
} }
...@@ -77,7 +78,7 @@ public abstract class Buffer { ...@@ -77,7 +78,7 @@ public abstract class Buffer {
* *
* @param flag The flag to remove. * @param flag The flag to remove.
*/ */
public final void clearFlag(int flag) { public final void clearFlag(@C.BufferFlags int flag) {
flags &= ~flag; flags &= ~flag;
} }
...@@ -87,7 +88,7 @@ public abstract class Buffer { ...@@ -87,7 +88,7 @@ public abstract class Buffer {
* @param flag The flag to check. * @param flag The flag to check.
* @return Whether the flag is set. * @return Whether the flag is set.
*/ */
protected final boolean getFlag(int flag) { protected final boolean getFlag(@C.BufferFlags int flag) {
return (flags & flag) == flag; return (flags & flag) == flag;
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.decoder; package com.google.android.exoplayer2.decoder;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /**
...@@ -34,6 +35,7 @@ public final class CryptoInfo { ...@@ -34,6 +35,7 @@ public final class CryptoInfo {
/** /**
* @see android.media.MediaCodec.CryptoInfo#mode * @see android.media.MediaCodec.CryptoInfo#mode
*/ */
@C.CryptoMode
public int mode; public int mode;
/** /**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
...@@ -58,7 +60,7 @@ public final class CryptoInfo { ...@@ -58,7 +60,7 @@ public final class CryptoInfo {
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/ */
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
byte[] key, byte[] iv, int mode) { byte[] key, byte[] iv, @C.CryptoMode int mode) {
this.numSubSamples = numSubSamples; this.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData; this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData; this.numBytesOfEncryptedData = numBytesOfEncryptedData;
......
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
*/ */
package com.google.android.exoplayer2.decoder; package com.google.android.exoplayer2.decoder;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
...@@ -24,15 +27,20 @@ import java.nio.ByteBuffer; ...@@ -24,15 +27,20 @@ import java.nio.ByteBuffer;
public class DecoderInputBuffer extends Buffer { public class DecoderInputBuffer extends Buffer {
/** /**
* The buffer replacement mode, which may disable replacement.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL,
BUFFER_REPLACEMENT_MODE_DIRECT})
public @interface BufferReplacementMode {}
/**
* Disallows buffer replacement. * Disallows buffer replacement.
*/ */
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
/** /**
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}. * Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
*/ */
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
/** /**
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
*/ */
...@@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer { ...@@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer {
*/ */
public long timeUs; public long timeUs;
@BufferReplacementMode
private final int bufferReplacementMode; private final int bufferReplacementMode;
/** /**
...@@ -60,7 +69,7 @@ public class DecoderInputBuffer extends Buffer { ...@@ -60,7 +69,7 @@ public class DecoderInputBuffer extends Buffer {
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/ */
public DecoderInputBuffer(int bufferReplacementMode) { public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) {
this.cryptoInfo = new CryptoInfo(); this.cryptoInfo = new CryptoInfo();
this.bufferReplacementMode = bufferReplacementMode; this.bufferReplacementMode = bufferReplacementMode;
} }
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A DRM session. * A DRM session.
...@@ -24,6 +27,12 @@ import android.annotation.TargetApi; ...@@ -24,6 +27,12 @@ import android.annotation.TargetApi;
public interface DrmSession<T extends ExoMediaCrypto> { public interface DrmSession<T extends ExoMediaCrypto> {
/** /**
* The state of the DRM session.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
@interface State {}
/**
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause. * The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
*/ */
int STATE_ERROR = 0; int STATE_ERROR = 0;
...@@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> { ...@@ -50,6 +59,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
*/ */
@State
int getState(); int getState();
/** /**
......
...@@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> { ...@@ -37,6 +37,6 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
/** /**
* Releases a {@link DrmSession}. * Releases a {@link DrmSession}.
*/ */
void releaseSession(DrmSession drmSession); void releaseSession(DrmSession<T> drmSession);
} }
...@@ -40,7 +40,7 @@ import java.util.UUID; ...@@ -40,7 +40,7 @@ import java.util.UUID;
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}. * A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
*/ */
@TargetApi(18) @TargetApi(18)
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager, public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
DrmSession<T> { DrmSession<T> {
/** /**
...@@ -87,6 +87,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm ...@@ -87,6 +87,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
private int openCount; private int openCount;
private boolean provisioningInProgress; private boolean provisioningInProgress;
@DrmSession.State
private int state; private int state;
private T mediaCrypto; private T mediaCrypto;
private Exception lastException; private Exception lastException;
...@@ -267,7 +268,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm ...@@ -267,7 +268,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
} }
@Override @Override
public void releaseSession(DrmSession session) { public void releaseSession(DrmSession<T> session) {
if (--openCount != 0) { if (--openCount != 0) {
return; return;
} }
...@@ -291,6 +292,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm ...@@ -291,6 +292,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
// DrmSession implementation. // DrmSession implementation.
@Override @Override
@DrmSession.State
public final int getState() { public final int getState() {
return state; return state;
} }
......
...@@ -15,12 +15,22 @@ ...@@ -15,12 +15,22 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Thrown when the requested DRM scheme is not supported. * Thrown when the requested DRM scheme is not supported.
*/ */
public final class UnsupportedDrmException extends Exception { public final class UnsupportedDrmException extends Exception {
/** /**
* The reason for the exception.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
public @interface Reason {}
/**
* The requested DRM scheme is unsupported by the device. * The requested DRM scheme is unsupported by the device.
*/ */
public static final int REASON_UNSUPPORTED_SCHEME = 1; public static final int REASON_UNSUPPORTED_SCHEME = 1;
...@@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception { ...@@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception {
/** /**
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. * Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/ */
@Reason
public final int reason; public final int reason;
/** /**
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/ */
public UnsupportedDrmException(int reason) { public UnsupportedDrmException(@Reason int reason) {
this.reason = reason; this.reason = reason;
} }
...@@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception { ...@@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception {
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
* @param cause The cause of this exception. * @param cause The cause of this exception.
*/ */
public UnsupportedDrmException(int reason, Exception cause) { public UnsupportedDrmException(@Reason int reason, Exception cause) {
super(cause); super(cause);
this.reason = reason; this.reason = reason;
} }
......
...@@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput { ...@@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput {
throws IOException, InterruptedException { throws IOException, InterruptedException {
ensureSpaceForPeek(length); ensureSpaceForPeek(length);
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
peekBufferLength += length - bytesPeeked;
while (bytesPeeked < length) { while (bytesPeeked < length) {
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
allowEndOfInput); allowEndOfInput);
...@@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput { ...@@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput {
} }
} }
peekBufferPosition += length; peekBufferPosition += length;
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
return true; return true;
} }
......
...@@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput {
long offset = extrasHolder.offset; long offset = extrasHolder.offset;
// Read the signal byte. // Read the signal byte.
scratch.reset(1);
readData(offset, scratch.data, 1); readData(offset, scratch.data, 1);
offset++; offset++;
byte signalByte = scratch.data[0]; byte signalByte = scratch.data[0];
...@@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput {
// Read the subsample count, if present. // Read the subsample count, if present.
int subsampleCount; int subsampleCount;
if (subsampleEncryption) { if (subsampleEncryption) {
scratch.reset(2);
readData(offset, scratch.data, 2); readData(offset, scratch.data, 2);
offset += 2; offset += 2;
scratch.setPosition(0);
subsampleCount = scratch.readUnsignedShort(); subsampleCount = scratch.readUnsignedShort();
} else { } else {
subsampleCount = 1; subsampleCount = 1;
...@@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
if (subsampleEncryption) { if (subsampleEncryption) {
int subsampleDataLength = 6 * subsampleCount; int subsampleDataLength = 6 * subsampleCount;
ensureCapacity(scratch, subsampleDataLength); scratch.reset(subsampleDataLength);
readData(offset, scratch.data, subsampleDataLength); readData(offset, scratch.data, subsampleDataLength);
offset += subsampleDataLength; offset += subsampleDataLength;
scratch.setPosition(0); scratch.setPosition(0);
...@@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
} }
/**
* Ensure that the passed {@link ParsableByteArray} is of at least the specified limit.
*/
private static void ensureCapacity(ParsableByteArray byteArray, int limit) {
if (byteArray.limit() < limit) {
byteArray.reset(new byte[limit], limit);
}
}
// Called by the loading thread. // Called by the loading thread.
/** /**
...@@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
@Override @Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
if (!startWriteOperation()) { if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs); infoQueue.commitSampleTimestamp(timeUs);
return; return;
...@@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput { ...@@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput {
} }
} }
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
byte[] encryptionKey) { int size, byte[] encryptionKey) {
Assertions.checkState(!upstreamFormatRequired); Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs); commitSampleTimestamp(timeUs);
timesUs[relativeWriteIndex] = timeUs; timesUs[relativeWriteIndex] = timeUs;
......
...@@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput { ...@@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput {
} }
@Override @Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
// Do nothing. // Do nothing.
} }
......
...@@ -72,6 +72,7 @@ public interface TrackOutput { ...@@ -72,6 +72,7 @@ public interface TrackOutput {
* whose metadata is being passed. * whose metadata is being passed.
* @param encryptionKey The encryption key associated with the sample. May be null. * @param encryptionKey The encryption key associated with the sample. May be null.
*/ */
void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey); void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey);
} }
...@@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor {
private static final int ID_CUE_TRACK_POSITIONS = 0xB7; private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
private static final int ID_CUE_CLUSTER_POSITION = 0xF1; private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
private static final int ID_LANGUAGE = 0x22B59C; private static final int ID_LANGUAGE = 0x22B59C;
private static final int ID_PROJECTION = 0x7670;
private static final int ID_PROJECTION_PRIVATE = 0x7672;
private static final int ID_STEREO_MODE = 0x53B8;
private static final int LACING_NONE = 0; private static final int LACING_NONE = 0;
private static final int LACING_XIPH = 1; private static final int LACING_XIPH = 1;
...@@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor {
private int[] blockLacingSampleSizes; private int[] blockLacingSampleSizes;
private int blockTrackNumber; private int blockTrackNumber;
private int blockTrackNumberLength; private int blockTrackNumberLength;
@C.BufferFlags
private int blockFlags; private int blockFlags;
// Sample reading state. // Sample reading state.
...@@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_CUE_POINT: case ID_CUE_POINT:
case ID_CUE_TRACK_POSITIONS: case ID_CUE_TRACK_POSITIONS:
case ID_BLOCK_GROUP: case ID_BLOCK_GROUP:
case ID_PROJECTION:
return EbmlReader.TYPE_MASTER; return EbmlReader.TYPE_MASTER;
case ID_EBML_READ_VERSION: case ID_EBML_READ_VERSION:
case ID_DOC_TYPE_READ_VERSION: case ID_DOC_TYPE_READ_VERSION:
...@@ -390,6 +395,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -390,6 +395,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_CUE_TIME: case ID_CUE_TIME:
case ID_CUE_CLUSTER_POSITION: case ID_CUE_CLUSTER_POSITION:
case ID_REFERENCE_BLOCK: case ID_REFERENCE_BLOCK:
case ID_STEREO_MODE:
return EbmlReader.TYPE_UNSIGNED_INT; return EbmlReader.TYPE_UNSIGNED_INT;
case ID_DOC_TYPE: case ID_DOC_TYPE:
case ID_CODEC_ID: case ID_CODEC_ID:
...@@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_SIMPLE_BLOCK: case ID_SIMPLE_BLOCK:
case ID_BLOCK: case ID_BLOCK:
case ID_CODEC_PRIVATE: case ID_CODEC_PRIVATE:
case ID_PROJECTION_PRIVATE:
return EbmlReader.TYPE_BINARY; return EbmlReader.TYPE_BINARY;
case ID_DURATION: case ID_DURATION:
case ID_SAMPLING_FREQUENCY: case ID_SAMPLING_FREQUENCY:
...@@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor { ...@@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor {
case ID_BLOCK_DURATION: case ID_BLOCK_DURATION:
blockDurationUs = scaleTimecodeToUs(value); blockDurationUs = scaleTimecodeToUs(value);
return; return;
case ID_STEREO_MODE:
int layout = (int) value;
switch (layout) {
case 0:
currentTrack.stereoMode = C.STEREO_MODE_MONO;
break;
case 1:
currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT;
break;
case 3:
currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM;
break;
default:
break;
}
return;
default: default:
return; return;
} }
...@@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor { ...@@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor {
currentTrack.codecPrivate = new byte[contentSize]; currentTrack.codecPrivate = new byte[contentSize];
input.readFully(currentTrack.codecPrivate, 0, contentSize); input.readFully(currentTrack.codecPrivate, 0, contentSize);
return; return;
case ID_PROJECTION_PRIVATE:
currentTrack.projectionData = new byte[contentSize];
input.readFully(currentTrack.projectionData, 0, contentSize);
return;
case ID_CONTENT_COMPRESSION_SETTINGS: case ID_CONTENT_COMPRESSION_SETTINGS:
// This extractor only supports header stripping, so the payload is the stripped bytes. // This extractor only supports header stripping, so the payload is the stripped bytes.
currentTrack.sampleStrippedBytes = new byte[contentSize]; currentTrack.sampleStrippedBytes = new byte[contentSize];
...@@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor {
samplePartitionCountRead = true; samplePartitionCountRead = true;
} }
int samplePartitionDataSize = samplePartitionCount * 4; int samplePartitionDataSize = samplePartitionCount * 4;
if (scratch.limit() < samplePartitionDataSize) { scratch.reset(samplePartitionDataSize);
scratch.reset(new byte[samplePartitionDataSize], samplePartitionDataSize);
}
input.readFully(scratch.data, 0, samplePartitionDataSize); input.readFully(scratch.data, 0, samplePartitionDataSize);
sampleBytesRead += samplePartitionDataSize; sampleBytesRead += samplePartitionDataSize;
scratch.setPosition(0);
scratch.setLimit(samplePartitionDataSize);
short subsampleCount = (short) (1 + (samplePartitionCount / 2)); short subsampleCount = (short) (1 + (samplePartitionCount / 2));
int subsampleDataSize = 2 + 6 * subsampleCount; int subsampleDataSize = 2 + 6 * subsampleCount;
if (encryptionSubsampleDataBuffer == null if (encryptionSubsampleDataBuffer == null
...@@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor {
public int displayWidth = Format.NO_VALUE; public int displayWidth = Format.NO_VALUE;
public int displayHeight = Format.NO_VALUE; public int displayHeight = Format.NO_VALUE;
public int displayUnit = DISPLAY_UNIT_PIXELS; public int displayUnit = DISPLAY_UNIT_PIXELS;
public byte[] projectionData = null;
@C.StereoMode
public int stereoMode = Format.NO_VALUE;
// Audio elements. Initially set to their default values. // Audio elements. Initially set to their default values.
public int channelCount = 1; public int channelCount = 1;
...@@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor {
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
String mimeType; String mimeType;
int maxInputSize = Format.NO_VALUE; int maxInputSize = Format.NO_VALUE;
int pcmEncoding = Format.NO_VALUE; @C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
switch (codecId) { switch (codecId) {
case CODEC_ID_VP8: case CODEC_ID_VP8:
...@@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor {
} }
Format format; Format format;
int selectionFlags = 0; @C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? Format.SELECTION_FLAG_DEFAULT : 0; selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
selectionFlags |= flagForced ? Format.SELECTION_FLAG_FORCED : 0; selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them // TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats. // into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) { if (MimeTypes.isAudio(mimeType)) {
...@@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor {
} }
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, drmInitData); Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, selectionFlags, language, drmInitData); Format.NO_VALUE, selectionFlags, language, drmInitData);
......
...@@ -131,9 +131,13 @@ public final class Mp3Extractor implements Extractor { ...@@ -131,9 +131,13 @@ 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) {
try {
synchronize(input, false);
} catch (EOFException e) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
} }
}
if (seeker == null) { if (seeker == null) {
seeker = setupSeeker(input); seeker = setupSeeker(input);
extractorOutput.seekMap(seeker); extractorOutput.seekMap(seeker);
...@@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor { ...@@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor {
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) { if (sampleBytesRemaining == 0) {
if (!maybeResynchronize(extractorInput)) { extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
} }
scratch.setPosition(0);
int sampleHeaderData = scratch.readInt();
if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK)
|| MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
extractorInput.skipFully(1);
synchronizedHeaderData = 0;
return RESULT_CONTINUE;
}
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
if (basisTimeUs == C.TIME_UNSET) { if (basisTimeUs == C.TIME_UNSET) {
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
...@@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor { ...@@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor {
return RESULT_CONTINUE; return RESULT_CONTINUE;
} }
/**
* Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary.
*/
private boolean maybeResynchronize(ExtractorInput extractorInput)
throws IOException, InterruptedException {
extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
return false;
}
scratch.setPosition(0);
int sampleHeaderData = scratch.readInt();
if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) {
int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData);
if (frameSize != C.LENGTH_UNSET) {
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
return true;
}
}
synchronizedHeaderData = 0;
extractorInput.skipFully(1);
return synchronizeCatchingEndOfInput(extractorInput);
}
private boolean synchronizeCatchingEndOfInput(ExtractorInput input)
throws IOException, InterruptedException {
// An EOFException will be raised if any peek operation was partially satisfied. If a seek
// operation resulted in reading from within the last frame, we may try to peek past the end of
// the file in a partially-satisfied read operation, so we need to catch the exception.
try {
return synchronize(input, false);
} catch (EOFException e) {
return false;
}
}
private boolean synchronize(ExtractorInput input, boolean sniffing) private boolean synchronize(ExtractorInput input, boolean sniffing)
throws IOException, InterruptedException { throws IOException, InterruptedException {
int searched = 0;
int validFrameCount = 0; int validFrameCount = 0;
int candidateSynchronizedHeaderData = 0; int candidateSynchronizedHeaderData = 0;
int peekedId3Bytes = 0; int peekedId3Bytes = 0;
int searchedBytes = 0;
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition(); input.resetPeekPosition();
if (input.getPosition() == 0) { if (input.getPosition() == 0) {
Id3Util.parseId3(input, gaplessInfoHolder); Id3Util.parseId3(input, gaplessInfoHolder);
...@@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor { ...@@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor {
} }
} }
while (true) { while (true) {
if (sniffing && searched == MAX_SNIFF_BYTES) { if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
return false; // We reached the end of the stream but found at least one valid frame.
} break;
if (!sniffing && searched == MAX_SYNC_BYTES) {
throw new ParserException("Searched too many bytes.");
}
if (!input.peekFully(scratch.data, 0, 4, true)) {
return false;
} }
scratch.setPosition(0); scratch.setPosition(0);
int headerData = scratch.readInt(); int headerData = scratch.readInt();
...@@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor { ...@@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor {
if ((candidateSynchronizedHeaderData != 0 if ((candidateSynchronizedHeaderData != 0
&& (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK))
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
// The header is invalid or doesn't match the candidate header. Try the next byte offset. // The header doesn't match the candidate header or is invalid. Try the next byte offset.
if (searchedBytes++ == searchLimitBytes) {
if (!sniffing) {
throw new ParserException("Searched too many bytes.");
}
return false;
}
validFrameCount = 0; validFrameCount = 0;
candidateSynchronizedHeaderData = 0; candidateSynchronizedHeaderData = 0;
searched++;
if (sniffing) { if (sniffing) {
input.resetPeekPosition(); input.resetPeekPosition();
input.advancePeekPosition(peekedId3Bytes + searched); input.advancePeekPosition(peekedId3Bytes + searchedBytes);
} else { } else {
input.skipFully(1); input.skipFully(1);
} }
} else { } else {
// The header is valid and matches the candidate header. // The header matches the candidate header and/or is valid.
validFrameCount++; validFrameCount++;
if (validFrameCount == 1) { if (validFrameCount == 1) {
MpegAudioHeader.populateHeader(headerData, synchronizedHeader); MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
...@@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor {
} }
// Prepare to read the synchronized frame. // Prepare to read the synchronized frame.
if (sniffing) { if (sniffing) {
input.skipFully(peekedId3Bytes + searched); input.skipFully(peekedId3Bytes + searchedBytes);
} else { } else {
input.resetPeekPosition(); input.resetPeekPosition();
} }
...@@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor { ...@@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor {
long position = input.getPosition(); long position = input.getPosition();
long length = input.getLength(); long length = input.getLength();
int headerData = 0;
Seeker seeker = null;
// Check if there is a Xing header. // Check if there is a Xing header.
int xingBase = (synchronizedHeader.version & 1) != 0 int xingBase = (synchronizedHeader.version & 1) != 0
? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1
: (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5
if (frame.limit() >= xingBase + 4) {
frame.setPosition(xingBase); frame.setPosition(xingBase);
int headerData = frame.readInt(); headerData = frame.readInt();
Seeker seeker = null; }
if (headerData == XING_HEADER || headerData == INFO_HEADER) { if (headerData == XING_HEADER || headerData == INFO_HEADER) {
seeker = XingSeeker.create(synchronizedHeader, frame, position, length); seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) {
...@@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor {
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24());
} }
input.skipFully(synchronizedHeader.frameSize); input.skipFully(synchronizedHeader.frameSize);
} else { } else if (frame.limit() >= 40) {
// Check if there is a VBRI header. // Check if there is a VBRI header.
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
headerData = frame.readInt(); headerData = frame.readInt();
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -38,6 +39,8 @@ import java.util.List; ...@@ -38,6 +39,8 @@ import java.util.List;
*/ */
/* package */ final class AtomParsers { /* package */ final class AtomParsers {
private static final String TAG = "AtomParsers";
private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
private static final int TYPE_soun = Util.getIntegerCodeForString("soun"); private static final int TYPE_soun = Util.getIntegerCodeForString("soun");
private static final int TYPE_text = Util.getIntegerCodeForString("text"); private static final int TYPE_text = Util.getIntegerCodeForString("text");
...@@ -248,11 +251,16 @@ import java.util.List; ...@@ -248,11 +251,16 @@ import java.util.List;
remainingTimestampOffsetChanges--; remainingTimestampOffsetChanges--;
} }
// Check all the expected samples have been seen. // If the stbl's child boxes are not consistent the container is malformed, but the stream may
Assertions.checkArgument(remainingSynchronizationSamples == 0); // still be playable.
Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0); if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0
Assertions.checkArgument(remainingSamplesInChunk == 0); || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) {
Assertions.checkArgument(remainingTimestampDeltaChanges == 0); Log.w(TAG, "Inconsistent stbl box for track " + track.id
+ ": remainingSynchronizationSamples " + remainingSynchronizationSamples
+ ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta
+ ", remainingSamplesInChunk " + remainingSamplesInChunk
+ ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges);
}
} else { } else {
long[] chunkOffsetsBytes = new long[chunkIterator.length]; long[] chunkOffsetsBytes = new long[chunkIterator.length];
int[] chunkSampleCounts = new int[chunkIterator.length]; int[] chunkSampleCounts = new int[chunkIterator.length];
...@@ -636,7 +644,7 @@ import java.util.List; ...@@ -636,7 +644,7 @@ import java.util.List;
0 /* subsample timing is absolute */); 0 /* subsample timing is absolute */);
} else if (childAtomType == Atom.TYPE_c608) { } else if (childAtomType == Atom.TYPE_c608) {
out.format = Format.createTextSampleFormat(Integer.toString(trackId), out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, 0, language, drmInitData); MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData);
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
...@@ -665,6 +673,7 @@ import java.util.List; ...@@ -665,6 +673,7 @@ import java.util.List;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
String mimeType = null; String mimeType = null;
byte[] projectionData = null; byte[] projectionData = null;
@C.StereoMode
int stereoMode = Format.NO_VALUE; int stereoMode = Format.NO_VALUE;
while (childPosition - position < size) { while (childPosition - position < size) {
parent.setPosition(childPosition); parent.setPosition(childPosition);
...@@ -889,7 +898,7 @@ import java.util.List; ...@@ -889,7 +898,7 @@ import java.util.List;
if (out.format == null && mimeType != null) { if (out.format == null && mimeType != null) {
// TODO: Determine the correct PCM encoding. // TODO: Determine the correct PCM encoding.
int pcmEncoding = @C.PcmEncoding int pcmEncoding =
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE; MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
...@@ -1169,6 +1178,7 @@ import java.util.List; ...@@ -1169,6 +1178,7 @@ import java.util.List;
public Format format; public Format format;
public int nalUnitLengthFieldLength; public int nalUnitLengthFieldLength;
@Track.Transformation
public int requiredSampleTransformation; public int requiredSampleTransformation;
public StsdData(int numberOfEntries) { public StsdData(int numberOfEntries) {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
...@@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil; ...@@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -65,6 +68,13 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -65,6 +68,13 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
/** /**
* Flags controlling the behavior of the extractor.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED})
public @interface Flags {}
/**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* The workaround overrides the sync frame flags in the stream, forcing them to false except for * The workaround overrides the sync frame flags in the stream, forcing them to false except for
* the first sample in each segment. * the first sample in each segment.
...@@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor {
* This flag does nothing if the stream is not a video stream. * This flag does nothing if the stream is not a video stream.
*/ */
public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
/** /**
* Flag to ignore any tfdt boxes in the stream. * Flag to ignore any tfdt boxes in the stream.
*/ */
public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2; public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2;
/** /**
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4 * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
* container. * container.
...@@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int STATE_READING_SAMPLE_CONTINUE = 4; private static final int STATE_READING_SAMPLE_CONTINUE = 4;
// Workarounds. // Workarounds.
@Flags
private final int flags; private final int flags;
private final Track sideloadedTrack; private final Track sideloadedTrack;
...@@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
/** /**
* @param flags Flags to allow parsing of faulty streams. * @param flags Flags that control the extractor's behavior.
*/ */
public FragmentedMp4Extractor(int flags) { public FragmentedMp4Extractor(@Flags int flags) {
this(flags, null); this(flags, null);
} }
/** /**
* @param flags Flags to allow parsing of faulty streams. * @param flags Flags that control the extractor's behavior.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor * @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data. * will not receive a moov box in the input data.
*/ */
public FragmentedMp4Extractor(int flags, Track sideloadedTrack) { public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) {
this.sideloadedTrack = sideloadedTrack; this.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
...@@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray, private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
int flags, byte[] extendedTypeScratch) throws ParserException { @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
int moofContainerChildrenSize = moof.containerChildren.size(); int moofContainerChildrenSize = moof.containerChildren.size();
for (int i = 0; i < moofContainerChildrenSize; i++) { for (int i = 0; i < moofContainerChildrenSize; i++) {
Atom.ContainerAtom child = moof.containerChildren.get(i); Atom.ContainerAtom child = moof.containerChildren.get(i);
...@@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a traf atom (defined in 14496-12). * Parses a traf atom (defined in 14496-12).
*/ */
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
int flags, byte[] extendedTypeScratch) throws ParserException { @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
if (trackBundle == null) { if (trackBundle == null) {
...@@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
int flags) { @Flags int flags) {
int trunCount = 0; int trunCount = 0;
int totalSampleCount = 0; int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren; List<LeafAtom> leafChildren = traf.leafChildren;
...@@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param trun The trun atom to decode. * @param trun The trun atom to decode.
* @return The starting position of samples for the next run. * @return The starting position of samples for the next run.
*/ */
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags, private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
ParsableByteArray trun, int trackRunStart) { @Flags int flags, ParsableByteArray trun, int trackRunStart) {
trun.setPosition(Atom.HEADER_SIZE); trun.setPosition(Atom.HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int atomFlags = Atom.parseFullAtomFlags(fullAtom); int atomFlags = Atom.parseFullAtomFlags(fullAtom);
...@@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L;
int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0)
| (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0);
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
byte[] encryptionKey = null; byte[] encryptionKey = null;
......
...@@ -100,13 +100,14 @@ import java.io.IOException; ...@@ -100,13 +100,14 @@ import java.io.IOException;
while (bytesSearched < bytesToSearch) { while (bytesSearched < bytesToSearch) {
// Read an atom header. // Read an atom header.
int headerSize = Atom.HEADER_SIZE; int headerSize = Atom.HEADER_SIZE;
buffer.reset(headerSize);
input.peekFully(buffer.data, 0, headerSize); input.peekFully(buffer.data, 0, headerSize);
buffer.setPosition(0);
long atomSize = buffer.readUnsignedInt(); long atomSize = buffer.readUnsignedInt();
int atomType = buffer.readInt(); int atomType = buffer.readInt();
if (atomSize == Atom.LONG_SIZE_PREFIX) { if (atomSize == Atom.LONG_SIZE_PREFIX) {
headerSize = Atom.LONG_HEADER_SIZE; headerSize = Atom.LONG_HEADER_SIZE;
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
buffer.setLimit(Atom.LONG_HEADER_SIZE);
atomSize = buffer.readUnsignedLongToLong(); atomSize = buffer.readUnsignedLongToLong();
} }
...@@ -139,9 +140,7 @@ import java.io.IOException; ...@@ -139,9 +140,7 @@ import java.io.IOException;
if (atomDataSize < 8) { if (atomDataSize < 8) {
return false; return false;
} }
if (buffer.capacity() < atomDataSize) { buffer.reset(atomDataSize);
buffer.reset(new byte[atomDataSize], atomDataSize);
}
input.peekFully(buffer.data, 0, atomDataSize); input.peekFully(buffer.data, 0, atomDataSize);
int brandsCount = atomDataSize / 4; int brandsCount = atomDataSize / 4;
for (int i = 0; i < brandsCount; i++) { for (int i = 0; i < brandsCount; i++) {
......
...@@ -15,8 +15,11 @@ ...@@ -15,8 +15,11 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Encapsulates information describing an MP4 track. * Encapsulates information describing an MP4 track.
...@@ -24,6 +27,12 @@ import com.google.android.exoplayer2.Format; ...@@ -24,6 +27,12 @@ import com.google.android.exoplayer2.Format;
public final class Track { public final class Track {
/** /**
* The transformation to apply to samples in the track, if any.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})
public @interface Transformation {}
/**
* A no-op sample transformation. * A no-op sample transformation.
*/ */
public static final int TRANSFORMATION_NONE = 0; public static final int TRANSFORMATION_NONE = 0;
...@@ -66,6 +75,7 @@ public final class Track { ...@@ -66,6 +75,7 @@ public final class Track {
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each * One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
* sample. * sample.
*/ */
@Transformation
public final int sampleTransformation; public final int sampleTransformation;
/** /**
...@@ -90,7 +100,7 @@ public final class Track { ...@@ -90,7 +100,7 @@ public final class Track {
public final int nalUnitLengthFieldLength; public final int nalUnitLengthFieldLength;
public Track(int id, int type, long timescale, long movieTimescale, long durationUs, public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
Format format, int sampleTransformation, Format format, @Transformation int sampleTransformation,
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
long[] editListDurations, long[] editListMediaTimes) { long[] editListDurations, long[] editListMediaTimes) {
this.id = id; this.id = id;
......
...@@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util; ...@@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util;
*/ */
public final int[] flags; public final int[] flags;
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) { public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs,
int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length);
......
...@@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
/** /**
* Extracts EIA-608 data from a RawCC file * Extracts CEA data from a RawCC file.
*/ */
public final class RawCcExtractor implements Extractor { public final class RawCcExtractor implements Extractor {
...@@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor { ...@@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor {
trackOutput = extractorOutput.track(0); trackOutput = extractorOutput.track(0);
extractorOutput.endTracks(); extractorOutput.endTracks();
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
null, Format.NO_VALUE, 0, null, null)); null, Format.NO_VALUE, 0, null, null));
} }
...@@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor { ...@@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor {
dataScratch.reset(); dataScratch.reset();
input.readFully(dataScratch.data, 0, 3); input.readFully(dataScratch.data, 0, 3);
// only accept EIA-608 packets which have validity (6th bit) == 1 and trackOutput.sampleData(dataScratch, 3);
// type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100 sampleBytesWritten += 3;
int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07;
if (ccValidityAndType == 0x04) {
trackOutput.sampleData(dataScratch, 2);
sampleBytesWritten += 2;
}
} }
if (sampleBytesWritten > 0) { if (sampleBytesWritten > 0) {
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef;
import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Default implementation for {@link ElementaryStreamReader.Factory}.
*/
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
/**
* Flags controlling what workarounds are enabled for elementary stream readers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM,
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE})
public @interface WorkaroundFlags {
}
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2;
public static final int WORKAROUND_IGNORE_H264_STREAM = 4;
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8;
public static final int WORKAROUND_MAP_BY_TYPE = 16;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1.
private final SparseBooleanArray trackIds;
@WorkaroundFlags
private final int workaroundFlags;
private Id3Reader id3Reader;
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
public DefaultStreamReaderFactory() {
this(0);
}
public DefaultStreamReaderFactory(int workaroundFlags) {
trackIds = new SparseBooleanArray();
this.workaroundFlags = workaroundFlags;
}
@Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType,
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See b/20261500.
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
}
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
if (trackIds.get(trackId)) {
return null;
}
trackIds.put(trackId, true);
switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new MpegAudioReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AAC:
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new Ac3Reader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
return new DtsReader(output.track(trackId), esInfo.language);
case TsExtractor.TS_STREAM_TYPE_H262:
return new H262Reader(output.track(trackId));
case TsExtractor.TS_STREAM_TYPE_H264:
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0
? null : new H264Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)),
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
case TsExtractor.TS_STREAM_TYPE_H265:
return new H265Reader(output.track(trackId),
new SeiReader(output.track(nextEmbeddedTrackId++)));
case TsExtractor.TS_STREAM_TYPE_ID3:
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) {
return id3Reader;
} else {
return new Id3Reader(output.track(nextEmbeddedTrackId++));
}
default:
return null;
}
}
}
...@@ -15,13 +15,60 @@ ...@@ -15,13 +15,60 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
/** /**
* Extracts individual samples from an elementary media stream, preserving original order. * Extracts individual samples from an elementary media stream, preserving original order.
*/ */
/* package */ abstract class ElementaryStreamReader { public abstract class ElementaryStreamReader {
/**
* Factory of {@link ElementaryStreamReader} instances.
*/
public interface Factory {
/**
* Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the
* stream type is not supported or if the stream already has a reader assigned to it.
*
* @param pid The pid for the PMT entry.
* @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining
* the type of the stream.
* @param esInfo The descriptor information linked to the elementary stream.
* @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the
* created readers.
* @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided
* pid. {@code null} if the stream is not supported or if it should be ignored.
*/
ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo,
ExtractorOutput output);
}
/**
* Holds descriptor information associated with an elementary stream.
*/
public static final class EsInfo {
public final int streamType;
public String language;
public byte[] descriptorBytes;
/**
* @param streamType The type of the stream as defined by the
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
* @param descriptorBytes The descriptor bytes associated to the stream.
*/
public EsInfo(int streamType, String language, byte[] descriptorBytes) {
this.streamType = streamType;
this.language = language;
this.descriptorBytes = descriptorBytes;
}
}
protected final TrackOutput output; protected final TrackOutput output;
......
...@@ -128,7 +128,7 @@ import java.util.Collections; ...@@ -128,7 +128,7 @@ import java.util.Collections;
if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) {
int bytesWrittenPastStartCode = limit - startCodeOffset; int bytesWrittenPastStartCode = limit - startCodeOffset;
if (foundFirstFrameInGroup) { if (foundFirstFrameInGroup) {
int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; @C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode;
output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null);
isKeyframe = false; isKeyframe = false;
...@@ -136,7 +136,7 @@ import java.util.Collections; ...@@ -136,7 +136,7 @@ import java.util.Collections;
if (startCodeValue == START_GROUP) { if (startCodeValue == START_GROUP) {
foundFirstFrameInGroup = false; foundFirstFrameInGroup = false;
isKeyframe = true; isKeyframe = true;
} else /* startCode == START_PICTURE */ { } else /* startCodeValue == START_PICTURE */ {
frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs);
framePosition = totalBytesWritten - bytesWrittenPastStartCode; framePosition = totalBytesWritten - bytesWrittenPastStartCode;
pesPtsUsAvailable = false; pesPtsUsAvailable = false;
......
...@@ -57,7 +57,7 @@ import java.util.List; ...@@ -57,7 +57,7 @@ import java.util.List;
/** /**
* @param output A {@link TrackOutput} to which H.264 samples should be written. * @param output A {@link TrackOutput} to which H.264 samples should be written.
* @param seiReader A reader for EIA-608 samples in SEI NAL units. * @param seiReader A reader for CEA-608 samples in SEI NAL units.
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames). * synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on * @param detectAccessUnits Whether to split the input stream into access units (samples) based on
...@@ -420,7 +420,7 @@ import java.util.List; ...@@ -420,7 +420,7 @@ import java.util.List;
} }
private void outputSample(int offset) { private void outputSample(int offset) {
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
int size = (int) (nalUnitStartPosition - samplePosition); int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null); output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
} }
......
...@@ -64,7 +64,7 @@ import java.util.Collections; ...@@ -64,7 +64,7 @@ import java.util.Collections;
/** /**
* @param output A {@link TrackOutput} to which H.265 samples should be written. * @param output A {@link TrackOutput} to which H.265 samples should be written.
* @param seiReader A reader for EIA-608 samples in SEI NAL units. * @param seiReader A reader for CEA-608 samples in SEI NAL units.
*/ */
public H265Reader(TrackOutput output, SeiReader seiReader) { public H265Reader(TrackOutput output, SeiReader seiReader) {
super(output); super(output);
...@@ -471,7 +471,7 @@ import java.util.Collections; ...@@ -471,7 +471,7 @@ import java.util.Collections;
} }
private void outputSample(int offset) { private void outputSample(int offset) {
int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
int size = (int) (nalUnitStartPosition - samplePosition); int size = (int) (nalUnitStartPosition - samplePosition);
output.sampleMetadata(sampleTimeUs, flags, size, offset, null); output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
} }
......
...@@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor { ...@@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor {
input.peekFully(psPacketBuffer.data, 0, 10); input.peekFully(psPacketBuffer.data, 0, 10);
// We only care about the pack_stuffing_length in here, skip the first 77 bits. // We only care about the pack_stuffing_length in here, skip the first 77 bits.
psPacketBuffer.setPosition(0); psPacketBuffer.setPosition(9);
psPacketBuffer.skipBytes(9);
// Last 3 bits is the length. // Last 3 bits is the length.
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07; int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
...@@ -209,7 +208,7 @@ public final class PsExtractor implements Extractor { ...@@ -209,7 +208,7 @@ public final class PsExtractor implements Extractor {
} }
} }
// The next 2 bytes are the length, once we have that we can consume the complete packet. // The next 2 bytes are the length. Once we have that we can consume the complete packet.
input.peekFully(psPacketBuffer.data, 0, 2); input.peekFully(psPacketBuffer.data, 0, 2);
psPacketBuffer.setPosition(0); psPacketBuffer.setPosition(0);
int payloadLength = psPacketBuffer.readUnsignedShort(); int payloadLength = psPacketBuffer.readUnsignedShort();
...@@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor { ...@@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor {
// Just skip this data. // Just skip this data.
input.skipFully(pesLength); input.skipFully(pesLength);
} else { } else {
if (psPacketBuffer.capacity() < pesLength) { psPacketBuffer.reset(pesLength);
// Reallocate for this and future packets.
psPacketBuffer.reset(new byte[pesLength], pesLength);
}
// Read the whole packet and the header for consumption. // Read the whole packet and the header for consumption.
input.readFully(psPacketBuffer.data, 0, pesLength); input.readFully(psPacketBuffer.data, 0, pesLength);
psPacketBuffer.setPosition(6); psPacketBuffer.setPosition(6);
psPacketBuffer.setLimit(pesLength);
payloadReader.consume(psPacketBuffer); payloadReader.consume(psPacketBuffer);
psPacketBuffer.setLimit(psPacketBuffer.capacity()); psPacketBuffer.setLimit(psPacketBuffer.capacity());
} }
......
...@@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.text.eia608.Eia608Decoder; import com.google.android.exoplayer2.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
/** /**
* Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}. * Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
*/ */
/* package */ final class SeiReader { /* package */ final class SeiReader {
...@@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
public SeiReader(TrackOutput output) { public SeiReader(TrackOutput output) {
this.output = output; this.output = output;
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, null, output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
Format.NO_VALUE, 0, null, null)); Format.NO_VALUE, 0, null, null));
} }
...@@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
payloadSize += b; payloadSize += b;
} while (b == 0xFF); } while (b == 0xFF);
// Process the payload. // Process the payload.
if (Eia608Decoder.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
// Ignore country_code (1) + provider_code (2) + user_identifier (4) // Ignore country_code (1) + provider_code (2) + user_identifier (4)
// + user_data_type_code (1). // + user_data_type_code (1).
seiBuffer.skipBytes(8); seiBuffer.skipBytes(8);
...@@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
seiBuffer.skipBytes(1); seiBuffer.skipBytes(1);
int sampleBytes = 0; int sampleBytes = 0;
for (int i = 0; i < ccCount; i++) { for (int i = 0; i < ccCount; i++) {
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07; int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07;
// Check that validity == 1 and type == 0. // Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1).
if (ccValidityAndType != 0x04) { if (ccValidityAndType != 0x04) {
seiBuffer.skipBytes(2); seiBuffer.skipBytes(3);
} else { } else {
sampleBytes += 2; sampleBytes += 3;
output.sampleData(seiBuffer, 2); output.sampleData(seiBuffer, 3);
} }
} }
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null); output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);
......
...@@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C;
/** Bits per sample for the audio data. */ /** Bits per sample for the audio data. */
private final int bitsPerSample; private final int bitsPerSample;
/** The PCM encoding */ /** The PCM encoding */
@C.PcmEncoding
private final int encoding; private final int encoding;
/** Offset to the start of sample data. */ /** Offset to the start of sample data. */
...@@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C; ...@@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C;
private long dataSize; private long dataSize;
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
int bitsPerSample, int encoding) { int bitsPerSample, @C.PcmEncoding int encoding) {
this.numChannels = numChannels; this.numChannels = numChannels;
this.sampleRateHz = sampleRateHz; this.sampleRateHz = sampleRateHz;
this.averageBytesPerSecond = averageBytesPerSecond; this.averageBytesPerSecond = averageBytesPerSecond;
...@@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C; ...@@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C;
} }
/** Returns the PCM encoding. **/ /** Returns the PCM encoding. **/
@C.PcmEncoding
public int getEncoding() { public int getEncoding() {
return encoding; return encoding;
} }
......
...@@ -87,7 +87,7 @@ import java.io.IOException; ...@@ -87,7 +87,7 @@ import java.io.IOException;
+ blockAlignment); + blockAlignment);
} }
int encoding = Util.getPcmEncoding(bitsPerSample); @C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample);
if (encoding == C.ENCODING_INVALID) { if (encoding == C.ENCODING_INVALID) {
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample); Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
return null; return null;
......
...@@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
MediaCrypto mediaCrypto = null; MediaCrypto mediaCrypto = null;
boolean drmSessionRequiresSecureDecoder = false; boolean drmSessionRequiresSecureDecoder = false;
if (drmSession != null) { if (drmSession != null) {
int drmSessionState = drmSession.getState(); @DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED } else if (drmSessionState == DrmSession.STATE_OPENED
...@@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (drmSession == null) { if (drmSession == null) {
return false; return false;
} }
int drmSessionState = drmSession.getState(); @DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} }
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
...@@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
int sourceIndex = timeline.getSourceIndexForPeriod(index); int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, callback, MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator,
allocator, positionUs); positionUs);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
return mediaPeriod; return mediaPeriod;
} }
......
...@@ -61,12 +61,15 @@ import java.util.Arrays; ...@@ -61,12 +61,15 @@ import java.util.Arrays;
private final Handler eventHandler; private final Handler eventHandler;
private final ExtractorMediaSource.EventListener eventListener; private final ExtractorMediaSource.EventListener eventListener;
private final MediaSource.Listener sourceListener; private final MediaSource.Listener sourceListener;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final Loader loader; private final Loader loader;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
private final ConditionVariable loadCondition; private final ConditionVariable loadCondition;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
private Callback callback;
private SeekMap seekMap; private SeekMap seekMap;
private boolean tracksBuilt; private boolean tracksBuilt;
private boolean prepared; private boolean prepared;
...@@ -85,6 +88,7 @@ import java.util.Arrays; ...@@ -85,6 +88,7 @@ import java.util.Arrays;
private int extractedSamplesCountAtStartOfLoad; private int extractedSamplesCountAtStartOfLoad;
private boolean loadingFinished; private boolean loadingFinished;
private boolean released;
/** /**
* @param uri The {@link Uri} of the media stream. * @param uri The {@link Uri} of the media stream.
...@@ -94,30 +98,41 @@ import java.util.Arrays; ...@@ -94,30 +98,41 @@ import java.util.Arrays;
* @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @param sourceListener A listener to notify when the timeline has been loaded. * @param sourceListener A listener to notify when the timeline has been loaded.
* @param callback A callback to receive updates from the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
*/ */
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener,
Callback callback, Allocator allocator) { Allocator allocator) {
this.uri = uri; this.uri = uri;
this.dataSource = dataSource; this.dataSource = dataSource;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.sourceListener = sourceListener; this.sourceListener = sourceListener;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
loader = new Loader("Loader:ExtractorMediaPeriod"); loader = new Loader("Loader:ExtractorMediaPeriod");
extractorHolder = new ExtractorHolder(extractors, this); extractorHolder = new ExtractorHolder(extractors, this);
loadCondition = new ConditionVariable(); loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = new Runnable() {
@Override
public void run() {
maybeFinishPrepare();
}
};
onContinueLoadingRequestedRunnable = new Runnable() {
@Override
public void run() {
if (!released) {
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this);
}
}
};
handler = new Handler();
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
sampleQueues = new DefaultTrackOutput[0]; sampleQueues = new DefaultTrackOutput[0];
length = C.LENGTH_UNSET; length = C.LENGTH_UNSET;
loadCondition.open();
startLoading();
} }
public void release() { public void release() {
...@@ -126,12 +141,21 @@ import java.util.Arrays; ...@@ -126,12 +141,21 @@ import java.util.Arrays;
@Override @Override
public void run() { public void run() {
extractorHolder.release(); extractorHolder.release();
}
});
for (DefaultTrackOutput sampleQueue : sampleQueues) { for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable(); sampleQueue.disable();
} }
} }
});
handler.removeCallbacksAndMessages(null);
released = true;
}
@Override
public void prepare(Callback callback) {
this.callback = callback;
loadCondition.open();
startLoading();
}
@Override @Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
...@@ -330,7 +354,7 @@ import java.util.Arrays; ...@@ -330,7 +354,7 @@ import java.util.Arrays;
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
} }
// ExtractorOutput implementation. // ExtractorOutput implementation. Called by the loading thread.
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id) {
...@@ -344,26 +368,26 @@ import java.util.Arrays; ...@@ -344,26 +368,26 @@ import java.util.Arrays;
@Override @Override
public void endTracks() { public void endTracks() {
tracksBuilt = true; tracksBuilt = true;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
@Override @Override
public void seekMap(SeekMap seekMap) { public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap; this.seekMap = seekMap;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// UpstreamFormatChangedListener implementation // UpstreamFormatChangedListener implementation. Called by the loading thread.
@Override @Override
public void onUpstreamFormatChanged(Format format) { public void onUpstreamFormatChanged(Format format) {
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// Internal methods. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
if (prepared || seekMap == null || !tracksBuilt) { if (released || prepared || seekMap == null || !tracksBuilt) {
return; return;
} }
for (DefaultTrackOutput sampleQueue : sampleQueues) { for (DefaultTrackOutput sampleQueue : sampleQueues) {
...@@ -576,7 +600,7 @@ import java.util.Arrays; ...@@ -576,7 +600,7 @@ import java.util.Arrays;
if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) {
position = input.getPosition(); position = input.getPosition();
loadCondition.close(); loadCondition.close();
callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); handler.post(onContinueLoadingRequestedRunnable);
} }
} }
} finally { } finally {
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List ...@@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(),
extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener,
this, callback, allocator); this, allocator);
} }
@Override @Override
......
...@@ -19,7 +19,6 @@ import android.util.Log; ...@@ -19,7 +19,6 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
...@@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource { ...@@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) { return childSource.createPeriod(index % childPeriodCount, allocator, positionUs);
return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs);
} }
@Override @Override
......
...@@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Called when preparation completes. * Called when preparation completes.
* <p> * <p>
* May be called from any thread. After invoking this method, the {@link MediaPeriod} can expect * Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be * for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
* called with the initial track selection. * called with the initial track selection.
* *
...@@ -43,6 +43,17 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -43,6 +43,17 @@ public interface MediaPeriod extends SequenceableLoader {
} }
/** /**
* Prepares this media period asynchronously.
* <p>
* {@code callback.onPrepared} is called when preparation completes. If preparation fails,
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
*
* @param callback Callback to receive updates from this period, including being notified when
* preparation completes.
*/
void prepare(Callback callback);
/**
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such * Throws an error that's preventing the period from becoming prepared. Does nothing if no such
* error exists. * error exists.
* <p> * <p>
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException; import java.io.IOException;
...@@ -54,18 +53,13 @@ public interface MediaSource { ...@@ -54,18 +53,13 @@ public interface MediaSource {
/** /**
* Returns a {@link MediaPeriod} corresponding to the period at the specified index. * Returns a {@link MediaPeriod} corresponding to the period at the specified index.
* <p>
* {@link Callback#onPrepared(MediaPeriod)} is called when the new period is prepared. If
* preparation fails, {@link MediaPeriod#maybeThrowPrepareError()} will throw an
* {@link IOException} if called on the returned instance.
* *
* @param index The index of the period. * @param index The index of the period.
* @param callback A callback to receive updates from the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The player's current playback position. * @param positionUs The player's current playback position.
* @return A new {@link MediaPeriod}. * @return A new {@link MediaPeriod}.
*/ */
MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, long positionUs); MediaPeriod createPeriod(int index, Allocator allocator, long positionUs);
/** /**
* Releases the period. * Releases the period.
......
...@@ -28,20 +28,27 @@ import java.util.IdentityHashMap; ...@@ -28,20 +28,27 @@ import java.util.IdentityHashMap;
public final MediaPeriod[] periods; public final MediaPeriod[] periods;
private final Callback callback;
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices; private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
private Callback callback;
private int pendingChildPrepareCount; private int pendingChildPrepareCount;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private MediaPeriod[] enabledPeriods; private MediaPeriod[] enabledPeriods;
private SequenceableLoader sequenceableLoader; private SequenceableLoader sequenceableLoader;
public MergingMediaPeriod(Callback callback, MediaPeriod... periods) { public MergingMediaPeriod(MediaPeriod... periods) {
this.periods = periods; this.periods = periods;
this.callback = callback;
streamPeriodIndices = new IdentityHashMap<>(); streamPeriodIndices = new IdentityHashMap<>();
}
@Override
public void prepare(Callback callback) {
this.callback = callback;
pendingChildPrepareCount = periods.length; pendingChildPrepareCount = periods.length;
for (MediaPeriod period : periods) {
period.prepare(this);
}
} }
@Override @Override
......
...@@ -15,11 +15,12 @@ ...@@ -15,11 +15,12 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -37,10 +38,15 @@ public final class MergingMediaSource implements MediaSource { ...@@ -37,10 +38,15 @@ public final class MergingMediaSource implements MediaSource {
public static final class IllegalMergeException extends IOException { public static final class IllegalMergeException extends IOException {
/** /**
* The reason the merge failed.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH})
public @interface Reason {}
/**
* The merge failed because one of the sources being merged has a dynamic window. * The merge failed because one of the sources being merged has a dynamic window.
*/ */
public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; public static final int REASON_WINDOWS_ARE_DYNAMIC = 0;
/** /**
* The merge failed because the sources have different period counts. * The merge failed because the sources have different period counts.
*/ */
...@@ -50,13 +56,14 @@ public final class MergingMediaSource implements MediaSource { ...@@ -50,13 +56,14 @@ public final class MergingMediaSource implements MediaSource {
* The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and * The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
* {@link #REASON_PERIOD_COUNT_MISMATCH}. * {@link #REASON_PERIOD_COUNT_MISMATCH}.
*/ */
@Reason
public final int reason; public final int reason;
/** /**
* @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and * @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and
* {@link #REASON_PERIOD_COUNT_MISMATCH}. * {@link #REASON_PERIOD_COUNT_MISMATCH}.
*/ */
public IllegalMergeException(int reason) { public IllegalMergeException(@Reason int reason) {
this.reason = reason; this.reason = reason;
} }
...@@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource { ...@@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
// The periods are only referenced after they have all been prepared.
MergingMediaPeriod mergingPeriod = new MergingMediaPeriod(callback, periods);
for (int i = 0; i < periods.length; i++) { for (int i = 0; i < periods.length; i++) {
periods[i] = mediaSources[i].createPeriod(index, mergingPeriod, allocator, positionUs); periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs);
Assertions.checkState(periods[i] != null, "Child source must not return null period");
} }
return mergingPeriod; return new MergingMediaPeriod(periods);
} }
@Override @Override
......
...@@ -29,7 +29,7 @@ public interface SequenceableLoader { ...@@ -29,7 +29,7 @@ public interface SequenceableLoader {
/** /**
* Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method * Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method
* to be called when it can continue to load data. * to be called when it can continue to load data. Called on the playback thread.
*/ */
void onContinueLoadingRequested(T source); void onContinueLoadingRequested(T source);
......
...@@ -79,6 +79,11 @@ import java.util.Arrays; ...@@ -79,6 +79,11 @@ import java.util.Arrays;
} }
@Override @Override
public void prepare(Callback callback) {
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
loader.maybeThrowError(); loader.maybeThrowError();
} }
......
...@@ -19,7 +19,6 @@ import android.net.Uri; ...@@ -19,7 +19,6 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource { ...@@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
MediaPeriod mediaPeriod = new SingleSampleMediaPeriod(uri, dataSourceFactory, format, return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount,
minLoadableRetryCount, eventHandler, eventListener, eventSourceId); eventHandler, eventListener, eventSourceId);
callback.onPrepared(mediaPeriod);
return mediaPeriod;
} }
@Override @Override
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.chunk; package com.google.android.exoplayer2.source.chunk;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput ...@@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
} }
@Override @Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
} }
......
...@@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad ...@@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
} }
@Override @Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
throw new IllegalStateException("Unexpected sample data in initialization chunk"); throw new IllegalStateException("Unexpected sample data in initialization chunk");
} }
......
...@@ -48,10 +48,10 @@ import java.util.List; ...@@ -48,10 +48,10 @@ import java.util.List;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final long elapsedRealtimeOffset; private final long elapsedRealtimeOffset;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams; private ChunkSampleStream<DashChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
private DashManifest manifest; private DashManifest manifest;
...@@ -61,7 +61,7 @@ import java.util.List; ...@@ -61,7 +61,7 @@ import java.util.List;
public DashMediaPeriod(int id, DashManifest manifest, int index, public DashMediaPeriod(int id, DashManifest manifest, int index,
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
EventDispatcher eventDispatcher, long elapsedRealtimeOffset, EventDispatcher eventDispatcher, long elapsedRealtimeOffset,
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
this.id = id; this.id = id;
this.manifest = manifest; this.manifest = manifest;
this.index = index; this.index = index;
...@@ -70,13 +70,11 @@ import java.util.List; ...@@ -70,13 +70,11 @@ import java.util.List;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.elapsedRealtimeOffset = elapsedRealtimeOffset;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
period = manifest.getPeriod(index); period = manifest.getPeriod(index);
trackGroups = buildTrackGroups(period); trackGroups = buildTrackGroups(period);
callback.onPrepared(this);
} }
public void updateManifest(DashManifest manifest, int index) { public void updateManifest(DashManifest manifest, int index) {
...@@ -98,6 +96,12 @@ import java.util.List; ...@@ -98,6 +96,12 @@ import java.util.List;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();
} }
......
...@@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
...@@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource { ...@@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index, DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index,
chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader, chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader,
callback, allocator); allocator);
periodsById.put(mediaPeriod.id, mediaPeriod); periodsById.put(mediaPeriod.id, mediaPeriod);
return mediaPeriod; return mediaPeriod;
} }
......
...@@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler ...@@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler
} else if (MimeTypes.isVideo(containerMimeType)) { } else if (MimeTypes.isVideo(containerMimeType)) {
return MimeTypes.getVideoMediaMimeType(codecs); return MimeTypes.getVideoMediaMimeType(codecs);
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
// We currently only support EIA-608 through RawCC // We currently only support CEA-608 through RawCC
if (codecs != null && codecs.contains("eia608")) { if (codecs != null
return MimeTypes.APPLICATION_EIA608; && (codecs.contains("eia608") || codecs.contains("cea608"))) {
return MimeTypes.APPLICATION_CEA608;
} }
return null; return null;
} else if (mimeTypeIsRawText(containerMimeType)) { } else if (mimeTypeIsRawText(containerMimeType)) {
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor; ...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster; import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
...@@ -355,20 +356,22 @@ import java.util.Locale; ...@@ -355,20 +356,22 @@ import java.util.Locale;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs); startTimeUs);
// This flag ensures the change of pid between streams does not affect the sample queues. // This flag ensures the change of pid between streams does not affect the sample queues.
int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE; @DefaultStreamReaderFactory.WorkaroundFlags
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
String codecs = variants[newVariantIndex].format.codecs; String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) { if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can explicitly // exist. If we know from the codec attribute that they don't exist, then we can explicitly
// ignore them even if they're declared. // ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM; workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_AAC_STREAM;
} }
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM; workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
} }
} }
extractor = new TsExtractor(timestampAdjuster, workaroundFlags); extractor = new TsExtractor(timestampAdjuster,
new DefaultStreamReaderFactory(workaroundFlags));
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor; extractor = previous.extractor;
......
...@@ -50,11 +50,11 @@ import java.util.List; ...@@ -50,11 +50,11 @@ import java.util.List;
/* package */ final class HlsMediaPeriod implements MediaPeriod, /* package */ final class HlsMediaPeriod implements MediaPeriod,
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback { Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsSampleStreamWrapper.Callback {
private final Uri manifestUri;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final MediaSource.Listener sourceListener; private final MediaSource.Listener sourceListener;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices; private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
...@@ -62,7 +62,9 @@ import java.util.List; ...@@ -62,7 +62,9 @@ import java.util.List;
private final Handler continueLoadingHandler; private final Handler continueLoadingHandler;
private final Loader manifestFetcher; private final Loader manifestFetcher;
private final long preparePositionUs; private final long preparePositionUs;
private final Runnable continueLoadingRunnable;
private Callback callback;
private int pendingPrepareCount; private int pendingPrepareCount;
private HlsPlaylist playlist; private HlsPlaylist playlist;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
...@@ -72,17 +74,16 @@ import java.util.List; ...@@ -72,17 +74,16 @@ import java.util.List;
private HlsSampleStreamWrapper[] sampleStreamWrappers; private HlsSampleStreamWrapper[] sampleStreamWrappers;
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
private Runnable continueLoadingRunnable;
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory, public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, int minLoadableRetryCount, EventDispatcher eventDispatcher,
MediaSource.Listener sourceListener, final Callback callback, Allocator allocator, MediaSource.Listener sourceListener, Allocator allocator,
long positionUs) { long positionUs) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.sourceListener = sourceListener; this.sourceListener = sourceListener;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
streamWrapperIndices = new IdentityHashMap<>(); streamWrapperIndices = new IdentityHashMap<>();
timestampAdjusterProvider = new TimestampAdjusterProvider(); timestampAdjusterProvider = new TimestampAdjusterProvider();
...@@ -96,11 +97,6 @@ import java.util.List; ...@@ -96,11 +97,6 @@ import java.util.List;
callback.onContinueLoadingRequested(HlsMediaPeriod.this); callback.onContinueLoadingRequested(HlsMediaPeriod.this);
} }
}; };
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
} }
public void release() { public void release() {
...@@ -112,6 +108,15 @@ import java.util.List; ...@@ -112,6 +108,15 @@ import java.util.List;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
if (sampleStreamWrappers == null) { if (sampleStreamWrappers == null) {
manifestFetcher.maybeThrowError(); manifestFetcher.maybeThrowError();
...@@ -239,13 +244,7 @@ import java.util.List; ...@@ -239,13 +244,7 @@ import java.util.List;
eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded()); loadDurationMs, loadable.bytesLoaded());
playlist = loadable.getResult(); playlist = loadable.getResult();
List<HlsSampleStreamWrapper> sampleStreamWrapperList = buildSampleStreamWrappers(); buildAndPrepareSampleStreamWrappers();
sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()];
sampleStreamWrapperList.toArray(sampleStreamWrappers);
pendingPrepareCount = sampleStreamWrappers.length;
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
sampleStreamWrapper.prepare();
}
} }
@Override @Override
...@@ -313,16 +312,16 @@ import java.util.List; ...@@ -313,16 +312,16 @@ import java.util.List;
// Internal methods. // Internal methods.
private List<HlsSampleStreamWrapper> buildSampleStreamWrappers() { private void buildAndPrepareSampleStreamWrappers() {
ArrayList<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
String baseUri = playlist.baseUri; String baseUri = playlist.baseUri;
if (playlist instanceof HlsMediaPlaylist) { if (playlist instanceof HlsMediaPlaylist) {
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] { HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)}; HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, sampleStreamWrappers = new HlsSampleStreamWrapper[] {
null, null)); buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)};
return sampleStreamWrappers; pendingPrepareCount = 1;
sampleStreamWrappers[0].prepare();
return;
} }
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
...@@ -351,32 +350,37 @@ import java.util.List; ...@@ -351,32 +350,37 @@ import java.util.List;
} else { } else {
// Leave the enabled variants unchanged. They're likely either all video or all audio. // Leave the enabled variants unchanged. They're likely either all video or all audio.
} }
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios;
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles;
sampleStreamWrappers = new HlsSampleStreamWrapper[(selectedVariants.isEmpty() ? 0 : 1)
+ audioVariants.size() + subtitleVariants.size()];
int currentWrapperIndex = 0;
pendingPrepareCount = sampleStreamWrappers.length;
if (!selectedVariants.isEmpty()) { if (!selectedVariants.isEmpty()) {
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()];
selectedVariants.toArray(variants); selectedVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT,
masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat)); baseUri, variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat);
sampleStreamWrapper.prepare();
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
} }
// Build the audio stream wrapper if applicable. // Build audio stream wrappers.
List<HlsMasterPlaylist.HlsUrl> audioVariants = masterPlaylist.audios; for (int i = 0; i < audioVariants.size(); i++) {
if (!audioVariants.isEmpty()) { HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO,
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[audioVariants.size()]; baseUri, new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null);
audioVariants.toArray(variants); sampleStreamWrapper.prepare();
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null, sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
null));
} }
// Build the text stream wrapper if applicable. // Build subtitle stream wrappers.
List<HlsMasterPlaylist.HlsUrl> subtitleVariants = masterPlaylist.subtitles; for (int i = 0; i < subtitleVariants.size(); i++) {
if (!subtitleVariants.isEmpty()) { HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i);
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[subtitleVariants.size()]; HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT,
subtitleVariants.toArray(variants); baseUri, new HlsMasterPlaylist.HlsUrl[] {url}, null, null);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null, sampleStreamWrapper.prepareSingleTrack(url.format);
null)); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
} }
return sampleStreamWrappers;
} }
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri, private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
......
...@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C; ...@@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
...@@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource { ...@@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource {
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount, return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount,
eventDispatcher, sourceListener, callback, allocator, positionUs); eventDispatcher, sourceListener, allocator, positionUs);
} }
@Override @Override
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.os.Handler;
import android.text.TextUtils;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -80,13 +82,15 @@ import java.util.LinkedList; ...@@ -80,13 +82,15 @@ import java.util.LinkedList;
private final HlsChunkSource.HlsChunkHolder nextChunkHolder; private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
private final SparseArray<DefaultTrackOutput> sampleQueues; private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks; private final LinkedList<HlsMediaChunk> mediaChunks;
private final Runnable maybeFinishPrepareRunnable;
private final Handler handler;
private volatile boolean sampleQueuesBuilt; private boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
private int enabledTrackCount; private int enabledTrackCount;
private Format downstreamTrackFormat; private Format downstreamTrackFormat;
private int upstreamChunkUid; private int upstreamChunkUid;
private boolean released;
// Tracks are complicated in HLS. See documentation of buildTracks for details. // Tracks are complicated in HLS. See documentation of buildTracks for details.
// Indexed by track (as exposed by this source). // Indexed by track (as exposed by this source).
...@@ -129,6 +133,13 @@ import java.util.LinkedList; ...@@ -129,6 +133,13 @@ import java.util.LinkedList;
nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
sampleQueues = new SparseArray<>(); sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>(); mediaChunks = new LinkedList<>();
maybeFinishPrepareRunnable = new Runnable() {
@Override
public void run() {
maybeFinishPrepare();
}
};
handler = new Handler();
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
} }
...@@ -137,6 +148,15 @@ import java.util.LinkedList; ...@@ -137,6 +148,15 @@ import java.util.LinkedList;
continueLoading(lastSeekPositionUs); continueLoading(lastSeekPositionUs);
} }
/**
* Prepares a sample stream wrapper for which the master playlist provides enough information to
* prepare.
*/
public void prepareSingleTrack(Format format) {
track(0).format(format);
endTracks();
}
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
maybeThrowError(); maybeThrowError();
} }
...@@ -245,6 +265,8 @@ import java.util.LinkedList; ...@@ -245,6 +265,8 @@ import java.util.LinkedList;
sampleQueues.valueAt(i).disable(); sampleQueues.valueAt(i).disable();
} }
loader.release(); loader.release();
handler.removeCallbacksAndMessages(null);
released = true;
} }
public long getLargestQueuedTimestampUs() { public long getLargestQueuedTimestampUs() {
...@@ -454,7 +476,7 @@ import java.util.LinkedList; ...@@ -454,7 +476,7 @@ import java.util.LinkedList;
@Override @Override
public void endTracks() { public void endTracks() {
sampleQueuesBuilt = true; sampleQueuesBuilt = true;
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
@Override @Override
...@@ -462,17 +484,17 @@ import java.util.LinkedList; ...@@ -462,17 +484,17 @@ import java.util.LinkedList;
// Do nothing. // Do nothing.
} }
// UpstreamFormatChangedListener implementation. // UpstreamFormatChangedListener implementation. Called by the loading thread.
@Override @Override
public void onUpstreamFormatChanged(Format format) { public void onUpstreamFormatChanged(Format format) {
maybeFinishPrepare(); handler.post(maybeFinishPrepareRunnable);
} }
// Internal methods. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
if (prepared || !sampleQueuesBuilt) { if (released || prepared || !sampleQueuesBuilt) {
return; return;
} }
int sampleQueueCount = sampleQueues.size(); int sampleQueueCount = sampleQueues.size();
...@@ -558,7 +580,7 @@ import java.util.LinkedList; ...@@ -558,7 +580,7 @@ import java.util.LinkedList;
if (i == primaryExtractorTrackIndex) { if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount]; Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) { for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat); formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
} }
trackGroups[i] = new TrackGroup(formats); trackGroups[i] = new TrackGroup(formats);
primaryTrackGroupIndex = i; primaryTrackGroupIndex = i;
...@@ -567,11 +589,11 @@ import java.util.LinkedList; ...@@ -567,11 +589,11 @@ import java.util.LinkedList;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
trackFormat = muxedAudioFormat; trackFormat = muxedAudioFormat;
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) { } else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) {
trackFormat = muxedCaptionFormat; trackFormat = muxedCaptionFormat;
} }
} }
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat)); trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
} }
} }
this.trackGroups = new TrackGroupArray(trackGroups); this.trackGroups = new TrackGroupArray(trackGroups);
...@@ -590,18 +612,25 @@ import java.util.LinkedList; ...@@ -590,18 +612,25 @@ import java.util.LinkedList;
} }
/** /**
* Derives a sample format corresponding to a given container format, by combining it with sample * Derives a track format corresponding to a given container format, by combining it with sample
* level information obtained from a second sample format. * level information obtained from the samples.
* *
* @param containerFormat The container format for which the sample format should be derived. * @param containerFormat The container format for which the track format should be derived.
* @param sampleFormat A sample format from which to obtain sample level information. * @param sampleFormat A sample format from which to obtain sample level information.
* @return The derived sample format. * @return The derived track format.
*/ */
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) { private static Format deriveFormat(Format containerFormat, Format sampleFormat) {
if (containerFormat == null) { if (containerFormat == null) {
return sampleFormat; return sampleFormat;
} }
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, String codecs = null;
int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType);
if (sampleTrackType == C.TRACK_TYPE_AUDIO) {
codecs = getAudioCodecs(containerFormat.codecs);
} else if (sampleTrackType == C.TRACK_TYPE_VIDEO) {
codecs = getVideoCodecs(containerFormat.codecs);
}
return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate,
containerFormat.width, containerFormat.height, containerFormat.selectionFlags, containerFormat.width, containerFormat.height, containerFormat.selectionFlags,
containerFormat.language); containerFormat.language);
} }
...@@ -614,4 +643,29 @@ import java.util.LinkedList; ...@@ -614,4 +643,29 @@ import java.util.LinkedList;
return pendingResetPositionUs != C.TIME_UNSET; return pendingResetPositionUs != C.TIME_UNSET;
} }
private static String getAudioCodecs(String codecs) {
return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO);
}
private static String getVideoCodecs(String codecs) {
return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO);
}
private static String getCodecsOfType(String codecs, int trackType) {
if (TextUtils.isEmpty(codecs)) {
return null;
}
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
StringBuilder builder = new StringBuilder();
for (String codec : codecArray) {
if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) {
if (builder.length() > 0) {
builder.append(",");
}
builder.append(codec);
}
}
return builder.length() > 0 ? builder.toString() : null;
}
} }
...@@ -15,18 +15,29 @@ ...@@ -15,18 +15,29 @@
*/ */
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Represents an HLS playlist. * Represents an HLS playlist.
*/ */
public abstract class HlsPlaylist { public abstract class HlsPlaylist {
/**
* The type of playlist.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_MASTER, TYPE_MEDIA})
public @interface Type {}
public static final int TYPE_MASTER = 0; public static final int TYPE_MASTER = 0;
public static final int TYPE_MEDIA = 1; public static final int TYPE_MEDIA = 1;
public final String baseUri; public final String baseUri;
@Type
public final int type; public final int type;
protected HlsPlaylist(String baseUri, int type) { protected HlsPlaylist(String baseUri, @Type int type) {
this.baseUri = baseUri; this.baseUri = baseUri;
this.type = type; this.type = type;
} }
......
...@@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String BOOLEAN_TRUE = "YES"; private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO"; private static final String BOOLEAN_FALSE = "NO";
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\""); private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\""); private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
...@@ -138,7 +137,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -138,7 +137,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
while (iterator.hasNext()) { while (iterator.hasNext()) {
line = iterator.next(); line = iterator.next();
if (line.startsWith(TAG_MEDIA)) { if (line.startsWith(TAG_MEDIA)) {
int selectionFlags = parseSelectionFlags(line); @C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
String uri = parseOptionalStringAttr(line, REGEX_URI); String uri = parseOptionalStringAttr(line, REGEX_URI);
String name = parseStringAttr(line, REGEX_NAME); String name = parseStringAttr(line, REGEX_NAME);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
...@@ -162,7 +161,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -162,7 +161,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
case TYPE_CLOSED_CAPTIONS: case TYPE_CLOSED_CAPTIONS:
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) { if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
muxedCaptionFormat = Format.createTextContainerFormat(name, muxedCaptionFormat = Format.createTextContainerFormat(name,
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE,
selectionFlags, language); selectionFlags, language);
} }
break; break;
...@@ -200,11 +199,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -200,11 +199,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
muxedCaptionFormat); muxedCaptionFormat);
} }
@C.SelectionFlags
private static int parseSelectionFlags(String line) { private static int parseSelectionFlags(String line) {
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0) return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0)
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0) | (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0)
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT | (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0);
: 0);
} }
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
......
...@@ -46,23 +46,22 @@ import java.util.ArrayList; ...@@ -46,23 +46,22 @@ import java.util.ArrayList;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final Callback callback;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private final TrackEncryptionBox[] trackEncryptionBoxes; private final TrackEncryptionBox[] trackEncryptionBoxes;
private Callback callback;
private SsManifest manifest; private SsManifest manifest;
private ChunkSampleStream<SsChunkSource>[] sampleStreams; private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, int minLoadableRetryCount, EventDispatcher eventDispatcher,
LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) {
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.callback = callback;
this.allocator = allocator; this.allocator = allocator;
trackGroups = buildTrackGroups(manifest); trackGroups = buildTrackGroups(manifest);
...@@ -77,7 +76,6 @@ import java.util.ArrayList; ...@@ -77,7 +76,6 @@ import java.util.ArrayList;
this.manifest = manifest; this.manifest = manifest;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
callback.onPrepared(this);
} }
public void updateManifest(SsManifest manifest) { public void updateManifest(SsManifest manifest) {
...@@ -95,6 +93,12 @@ import java.util.ArrayList; ...@@ -95,6 +93,12 @@ import java.util.ArrayList;
} }
@Override @Override
public void prepare(Callback callback) {
this.callback = callback;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError(); manifestLoaderErrorThrower.maybeThrowError();
} }
......
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline; ...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
...@@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource, ...@@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource,
} }
@Override @Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
long positionUs) {
Assertions.checkArgument(index == 0); Assertions.checkArgument(index == 0);
SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount,
eventDispatcher, manifestLoader, callback, allocator); eventDispatcher, manifestLoader, allocator);
mediaPeriods.add(period); mediaPeriods.add(period);
return period; return period;
} }
......
...@@ -18,9 +18,12 @@ package com.google.android.exoplayer2.text; ...@@ -18,9 +18,12 @@ package com.google.android.exoplayer2.text;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle; import android.view.accessibility.CaptioningManager.CaptionStyle;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A compatibility wrapper for {@link CaptionStyle}. * A compatibility wrapper for {@link CaptionStyle}.
...@@ -28,25 +31,28 @@ import com.google.android.exoplayer2.util.Util; ...@@ -28,25 +31,28 @@ import com.google.android.exoplayer2.util.Util;
public final class CaptionStyleCompat { public final class CaptionStyleCompat {
/** /**
* The type of edge, which may be none.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED,
EDGE_TYPE_DEPRESSED})
public @interface EdgeType {}
/**
* Edge type value specifying no character edges. * Edge type value specifying no character edges.
*/ */
public static final int EDGE_TYPE_NONE = 0; public static final int EDGE_TYPE_NONE = 0;
/** /**
* Edge type value specifying uniformly outlined character edges. * Edge type value specifying uniformly outlined character edges.
*/ */
public static final int EDGE_TYPE_OUTLINE = 1; public static final int EDGE_TYPE_OUTLINE = 1;
/** /**
* Edge type value specifying drop-shadowed character edges. * Edge type value specifying drop-shadowed character edges.
*/ */
public static final int EDGE_TYPE_DROP_SHADOW = 2; public static final int EDGE_TYPE_DROP_SHADOW = 2;
/** /**
* Edge type value specifying raised bevel character edges. * Edge type value specifying raised bevel character edges.
*/ */
public static final int EDGE_TYPE_RAISED = 3; public static final int EDGE_TYPE_RAISED = 3;
/** /**
* Edge type value specifying depressed bevel character edges. * Edge type value specifying depressed bevel character edges.
*/ */
...@@ -88,6 +94,7 @@ public final class CaptionStyleCompat { ...@@ -88,6 +94,7 @@ public final class CaptionStyleCompat {
* <li>{@link #EDGE_TYPE_DEPRESSED} * <li>{@link #EDGE_TYPE_DEPRESSED}
* </ul> * </ul>
*/ */
@EdgeType
public final int edgeType; public final int edgeType;
/** /**
...@@ -126,8 +133,8 @@ public final class CaptionStyleCompat { ...@@ -126,8 +133,8 @@ public final class CaptionStyleCompat {
* @param edgeColor See {@link #edgeColor}. * @param edgeColor See {@link #edgeColor}.
* @param typeface See {@link #typeface}. * @param typeface See {@link #typeface}.
*/ */
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType, public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor,
int edgeColor, Typeface typeface) { @EdgeType int edgeType, int edgeColor, Typeface typeface) {
this.foregroundColor = foregroundColor; this.foregroundColor = foregroundColor;
this.backgroundColor = backgroundColor; this.backgroundColor = backgroundColor;
this.windowColor = windowColor; this.windowColor = windowColor;
...@@ -137,6 +144,7 @@ public final class CaptionStyleCompat { ...@@ -137,6 +144,7 @@ public final class CaptionStyleCompat {
} }
@TargetApi(19) @TargetApi(19)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV19( private static CaptionStyleCompat createFromCaptionStyleV19(
CaptioningManager.CaptionStyle captionStyle) { CaptioningManager.CaptionStyle captionStyle) {
return new CaptionStyleCompat( return new CaptionStyleCompat(
...@@ -145,6 +153,7 @@ public final class CaptionStyleCompat { ...@@ -145,6 +153,7 @@ public final class CaptionStyleCompat {
} }
@TargetApi(21) @TargetApi(21)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV21( private static CaptionStyleCompat createFromCaptionStyleV21(
CaptioningManager.CaptionStyle captionStyle) { CaptioningManager.CaptionStyle captionStyle) {
return new CaptionStyleCompat( return new CaptionStyleCompat(
......
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
*/ */
package com.google.android.exoplayer2.text; package com.google.android.exoplayer2.text;
import android.support.annotation.IntDef;
import android.text.Layout.Alignment; import android.text.Layout.Alignment;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Contains information about a specific cue, including textual content and formatting data. * Contains information about a specific cue, including textual content and formatting data.
...@@ -26,6 +29,13 @@ public class Cue { ...@@ -26,6 +29,13 @@ public class Cue {
* An unset position or width. * An unset position or width.
*/ */
public static final float DIMEN_UNSET = Float.MIN_VALUE; public static final float DIMEN_UNSET = Float.MIN_VALUE;
/**
* The type of anchor, which may be unset.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
public @interface AnchorType {}
/** /**
* An unset anchor or line type value. * An unset anchor or line type value.
*/ */
...@@ -44,6 +54,13 @@ public class Cue { ...@@ -44,6 +54,13 @@ public class Cue {
* box. * box.
*/ */
public static final int ANCHOR_TYPE_END = 2; public static final int ANCHOR_TYPE_END = 2;
/**
* The type of line, which may be unset.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
public @interface LineType {}
/** /**
* Value for {@link #lineType} when {@link #line} is a fractional position. * Value for {@link #lineType} when {@link #line} is a fractional position.
*/ */
...@@ -83,6 +100,7 @@ public class Cue { ...@@ -83,6 +100,7 @@ public class Cue {
* -1). For horizontal text the size of the first line of the cue is its height, and the start * -1). For horizontal text the size of the first line of the cue is its height, and the start
* and end of the viewport are the top and bottom respectively. * and end of the viewport are the top and bottom respectively.
*/ */
@LineType
public final int lineType; public final int lineType;
/** /**
* The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, * The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START},
...@@ -92,6 +110,7 @@ public class Cue { ...@@ -92,6 +110,7 @@ public class Cue {
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
* respectively. * respectively.
*/ */
@AnchorType
public final int lineAnchor; public final int lineAnchor;
/** /**
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in * The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
...@@ -110,6 +129,7 @@ public class Cue { ...@@ -110,6 +129,7 @@ public class Cue {
* and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box
* respectively. * respectively.
*/ */
@AnchorType
public final int positionAnchor; public final int positionAnchor;
/** /**
* The size of the cue box in the writing direction specified as a fraction of the viewport size * The size of the cue box in the writing direction specified as a fraction of the viewport size
...@@ -137,8 +157,8 @@ public class Cue { ...@@ -137,8 +157,8 @@ public class Cue {
* @param positionAnchor See {@link #positionAnchor}. * @param positionAnchor See {@link #positionAnchor}.
* @param size See {@link #size}. * @param size See {@link #size}.
*/ */
public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor, public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
float position, int positionAnchor, float size) { @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
this.text = text; this.text = text;
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.line = line; this.line = line;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.text; package com.google.android.exoplayer2.text;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.eia608.Eia608Decoder; import com.google.android.exoplayer2.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.text.subrip.SubripDecoder; import com.google.android.exoplayer2.text.subrip.SubripDecoder;
import com.google.android.exoplayer2.text.ttml.TtmlDecoder; import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
...@@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory { ...@@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory {
* <li>TTML ({@link TtmlDecoder})</li> * <li>TTML ({@link TtmlDecoder})</li>
* <li>SubRip ({@link SubripDecoder})</li> * <li>SubRip ({@link SubripDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})</li> * <li>TX3G ({@link Tx3gDecoder})</li>
* <li>Eia608 ({@link Eia608Decoder})</li> * <li>Cea608 ({@link Cea608Decoder})</li>
* </ul> * </ul>
*/ */
SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() {
...@@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory { ...@@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory {
return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder"); return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder");
case MimeTypes.APPLICATION_TX3G: case MimeTypes.APPLICATION_TX3G:
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
case MimeTypes.APPLICATION_EIA608: case MimeTypes.APPLICATION_CEA608:
return Class.forName("com.google.android.exoplayer2.text.eia608.Eia608Decoder"); return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
default: default:
return null; return null;
} }
......
...@@ -13,26 +13,22 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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,27 +236,34 @@ public final class Eia608Decoder implements SubtitleDecoder { ...@@ -320,27 +236,34 @@ 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) {
// Extended Spanish/Miscellaneous and French character set (S = 0).
if (ccData1 == 0x12 || ccData1 == 0x1A) {
// TODO: Make use of the channel bit
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedEsFrChar(ccData2)); captionStringBuilder.append(getExtendedEsFrChar(ccData2));
continue; 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.
if (ccData1 < 0x20) { if (ccData1 < 0x20) {
...@@ -367,16 +290,18 @@ public final class Eia608Decoder implements SubtitleDecoder { ...@@ -367,16 +290,18 @@ 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) {
if (repeatableControlSet
&& repeatableControlCc1 == cc1 && repeatableControlCc1 == cc1
&& repeatableControlCc2 == cc2) { && repeatableControlCc2 == cc2) {
repeatableControlSet = false; repeatableControlSet = false;
return true; return true;
} else if (isRepeatableControl) { } else {
repeatableControlSet = true; repeatableControlSet = true;
repeatableControlCc1 = cc1; repeatableControlCc1 = cc1;
repeatableControlCc2 = cc2; repeatableControlCc2 = cc2;
} }
}
if (isMiscCode(cc1, cc2)) { if (isMiscCode(cc1, cc2)) {
handleMiscCode(cc2); handleMiscCode(cc2);
} else if (isPreambleAddressCode(cc1, cc2)) { } else if (isPreambleAddressCode(cc1, cc2)) {
...@@ -526,16 +451,16 @@ public final class Eia608Decoder implements SubtitleDecoder { ...@@ -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;
......
/*
* 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 @@ ...@@ -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 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -242,6 +260,7 @@ import java.util.List;
return this; return this;
} }
@FontSizeUnit
public int getFontSizeUnit() { public int getFontSizeUnit() {
return fontSizeUnit; return fontSizeUnit;
} }
......
...@@ -38,7 +38,8 @@ import com.google.android.exoplayer2.text.Cue; ...@@ -38,7 +38,8 @@ import com.google.android.exoplayer2.text.Cue;
} }
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,206 +18,356 @@ package com.google.android.exoplayer2.trackselection; ...@@ -18,206 +18,356 @@ 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 {
/** /**
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the * Holder for available configurations for the {@link DefaultTrackSelector}.
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
* dimension).
*/ */
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; public static final class Parameters {
private static final int[] NO_TRACKS = new int[0];
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
// Audio. // Audio.
private String preferredAudioLanguage; public final String preferredAudioLanguage;
// Text. // Text.
private String preferredTextLanguage; public final String preferredTextLanguage;
// Video. // Video.
private boolean allowMixedMimeAdaptiveness; public final boolean allowMixedMimeAdaptiveness;
private boolean allowNonSeamlessAdaptiveness; public final boolean allowNonSeamlessAdaptiveness;
private int maxVideoWidth; public final int maxVideoWidth;
private int maxVideoHeight; public final int maxVideoHeight;
private boolean exceedVideoConstraintsIfNecessary; public final boolean exceedVideoConstraintsIfNecessary;
private boolean orientationMayChange; public final int viewportWidth;
private int viewportWidth; public final int viewportHeight;
private int viewportHeight; public final boolean orientationMayChange;
/** /**
* Constructs an instance that does not support adaptive video. * Constructor with default selection parameters:
* * <ul>
* @param eventHandler A handler to use when delivering events to listeners. May be null if * <li>No preferred audio language is set.</li>
* listeners will not be added. * <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 DefaultTrackSelector(Handler eventHandler) { public Parameters() {
this(eventHandler, null); this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE,
Integer.MAX_VALUE, true);
} }
/** /**
* Constructs an instance that uses a factory to create adaptive video track selections. * @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
* @param eventHandler A handler to use when delivering events to listeners. May be null if * if there's no default.
* listeners will not be added. * @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646.
* @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s, * {@code null} to select the default track, or first track if there's no default.
* or null if the selector should not support adaptive video. * @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 DefaultTrackSelector(Handler eventHandler, public Parameters(String preferredAudioLanguage, String preferredTextLanguage,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness,
super(eventHandler); int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary,
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; int viewportWidth, int viewportHeight, boolean orientationMayChange) {
allowNonSeamlessAdaptiveness = true; this.preferredAudioLanguage = preferredAudioLanguage;
exceedVideoConstraintsIfNecessary = true; this.preferredTextLanguage = preferredTextLanguage;
maxVideoWidth = Integer.MAX_VALUE; this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness;
maxVideoHeight = Integer.MAX_VALUE; this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness;
viewportWidth = Integer.MAX_VALUE; this.maxVideoWidth = maxVideoWidth;
viewportHeight = Integer.MAX_VALUE; this.maxVideoHeight = maxVideoHeight;
orientationMayChange = true; this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.orientationMayChange = orientationMayChange;
} }
/** /**
* Sets the preferred language for audio, as well as for forced text tracks. * 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 * @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. * 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 void setPreferredLanguages(String preferredAudioLanguage) { public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) {
preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage);
if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) { if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) {
this.preferredAudioLanguage = preferredAudioLanguage; return this;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight,
exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange);
} }
/** /**
* Sets the preferred language for text tracks. * 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 * @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. * 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 void setPreferredTextLanguage(String preferredTextLanguage) { public Parameters withPreferredTextLanguage(String preferredTextLanguage) {
preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage);
if (!Util.areEqual(this.preferredTextLanguage, preferredTextLanguage)) { if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) {
this.preferredTextLanguage = preferredTextLanguage; return this;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Sets whether to allow selections to contain mixed mime types. * Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
* *
* @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types.
* @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance.
*/ */
public void allowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
if (this.allowMixedMimeAdaptiveness != allowMixedMimeAdaptiveness) { if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) {
this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; return this;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Sets whether non-seamless adaptation is allowed. * Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance.
* *
* @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed.
* @return A {@link Parameters} instance with the provided seamless adaptiveness allowance.
*/ */
public void allowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
if (this.allowNonSeamlessAdaptiveness != allowNonSeamlessAdaptiveness) { if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) {
this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; return this;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Sets the maximum allowed size for video tracks. * Returns a {@link Parameters} instance with the provided max video size.
* *
* @param maxVideoWidth Maximum allowed width. * @param maxVideoWidth The max video width.
* @param maxVideoHeight Maximum allowed height. * @param maxVideoHeight The max video width.
* @return A {@link Parameters} instance with the provided max video size.
*/ */
public void setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
if (this.maxVideoWidth != maxVideoWidth || this.maxVideoHeight != maxVideoHeight) { if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) {
this.maxVideoWidth = maxVideoWidth; return this;
this.maxVideoHeight = maxVideoHeight;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Equivalent to {@code setMaxVideoSize(1279, 719)}. * Equivalent to {@code withMaxVideoSize(1279, 719)}.
*
* @return A {@link Parameters} instance with maximum standard definition as maximum video size.
*/ */
public void setMaxVideoSizeSd() { public Parameters withMaxVideoSizeSd() {
setMaxVideoSize(1279, 719); return withMaxVideoSize(1279, 719);
} }
/** /**
* Equivalent to {@code setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. * Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
*
* @return A {@link Parameters} instance without video size constraints.
*/ */
public void clearMaxVideoSize() { public Parameters withoutVideoSizeConstraints() {
setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
} }
/** /**
* Sets whether video constraints should be ignored when no selection can be made otherwise. * Returns a {@link Parameters} instance with the provided
* {@code exceedVideoConstraintsIfNecessary} value.
* *
* @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections
* can be made otherwise. False to force constraints anyway. * can be made otherwise. False to force constraints anyway.
* @return A {@link Parameters} instance with the provided
* {@code exceedVideoConstraintsIfNecessary} value.
*/ */
public void setExceedVideoConstraintsIfNecessary(boolean exceedVideoConstraintsIfNecessary) { public Parameters withExceedVideoConstraintsIfNecessary(
if (this.exceedVideoConstraintsIfNecessary != exceedVideoConstraintsIfNecessary) { boolean exceedVideoConstraintsIfNecessary) {
this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) {
invalidate(); return this;
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Sets the target viewport size for selecting video tracks. * Returns a {@link Parameters} instance with the provided viewport size.
* *
* @param viewportWidth Viewport width in pixels. * @param viewportWidth Viewport width in pixels.
* @param viewportHeight Viewport height in pixels. * @param viewportHeight Viewport height in pixels.
* @param orientationMayChange Whether orientation may change during playback. * @param orientationMayChange Whether orientation may change during playback.
* @return A {@link Parameters} instance with the provided viewport size.
*/ */
public void setViewportSize(int viewportWidth, int viewportHeight, boolean orientationMayChange) { public Parameters withViewportSize(int viewportWidth, int viewportHeight,
if (this.viewportWidth != viewportWidth || this.viewportHeight != viewportHeight boolean orientationMayChange) {
|| this.orientationMayChange != orientationMayChange) { if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight
this.viewportWidth = viewportWidth; && orientationMayChange == this.orientationMayChange) {
this.viewportHeight = viewportHeight; return this;
this.orientationMayChange = orientationMayChange;
invalidate();
} }
return new Parameters(preferredAudioLanguage, preferredTextLanguage,
allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth,
maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight,
orientationMayChange);
} }
/** /**
* Retrieves the viewport size from the provided {@link Context} and calls * Returns a {@link Parameters} instance where the viewport size is obtained from the provided
* {@link #setViewportSize(int, int, boolean)} with this information. * {@link Context}.
* *
* @param context The context to obtain the viewport size from. * @param context The context to obtain the viewport size from.
* @param orientationMayChange Whether orientation may change during playback. * @param orientationMayChange Whether orientation may change during playback.
* @return A {@link Parameters} instance where the viewport size is obtained from the provided
* {@link Context}.
*/
public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) {
// Assume the viewport is fullscreen.
Point viewportSize = Util.getPhysicalDisplaySize(context);
return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
}
/**
* Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
*
* @return A {@link Parameters} instance without viewport size constraints.
*/
public Parameters withoutViewportSizeConstraints() {
return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Parameters other = (Parameters) obj;
return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness
&& allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness
&& maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight
&& exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary
&& orientationMayChange == other.orientationMayChange
&& viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
&& TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage)
&& TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage);
}
@Override
public int hashCode() {
int result = preferredAudioLanguage.hashCode();
result = 31 * result + preferredTextLanguage.hashCode();
result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0);
result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0);
result = 31 * result + maxVideoWidth;
result = 31 * result + maxVideoHeight;
result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0);
result = 31 * result + (orientationMayChange ? 1 : 0);
result = 31 * result + viewportWidth;
result = 31 * result + viewportHeight;
return result;
}
}
/**
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
* dimension).
*/
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f;
private static final int[] NO_TRACKS = new int[0];
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private final AtomicReference<Parameters> params;
/**
* Constructs an instance that does not support adaptive video.
*
* @param eventHandler A handler to use when delivering events to listeners. May be null if
* listeners will not be added.
*/ */
public void setViewportSizeFromContext(Context context, boolean orientationMayChange) { public DefaultTrackSelector(Handler eventHandler) {
Point viewportSize = Util.getPhysicalDisplaySize(context); // Assume the viewport is fullscreen. this(eventHandler, null);
setViewportSize(viewportSize.x, viewportSize.y, orientationMayChange);
} }
/** /**
* Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. * Constructs an instance that uses a factory to create adaptive video track selections.
*
* @param eventHandler A handler to use when delivering events to listeners. May be null if
* listeners will not be added.
* @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s,
* or null if the selector should not support adaptive video.
*/
public DefaultTrackSelector(Handler eventHandler,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
super(eventHandler);
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
params = new AtomicReference<>(new Parameters());
}
/**
* Atomically sets the provided parameters for track selection.
*
* @param params The parameters for track selection.
*/
public void setParameters(Parameters params) {
if (!this.params.get().equals(params)) {
this.params.set(Assertions.checkNotNull(params));
invalidate();
}
}
/**
* Gets the current selection parameters.
*
* @return The current selection parameters.
*/ */
public void clearViewportConstraints() { public Parameters getParameters() {
setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); return params.get();
} }
// MappingTrackSelector implementation. // MappingTrackSelector implementation.
...@@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
......
...@@ -16,39 +16,23 @@ ...@@ -16,39 +16,23 @@
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import android.os.Handler; import android.os.Handler;
import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
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.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/** /**
* Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s
* and renderers, and then from that mapping create a {@link TrackSelection} for each renderer. * and renderers, and then from that mapping create a {@link TrackSelection} for each renderer.
*/ */
public abstract class MappingTrackSelector extends TrackSelector { public abstract class MappingTrackSelector extends TrackSelector<MappedTrackInfo> {
/**
* Listener of {@link MappingTrackSelector} events.
*/
public interface EventListener {
/**
* Called when the track information has changed.
*
* @param trackInfo Contains the new track and track selection information.
*/
void onTracksChanged(TrackInfo trackInfo);
}
/** /**
* A track selection override. * A track selection override.
...@@ -96,52 +80,20 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -96,52 +80,20 @@ public abstract class MappingTrackSelector extends TrackSelector {
} }
private final Handler eventHandler;
private final CopyOnWriteArraySet<EventListener> listeners;
private final SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides; private final SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides;
private final SparseBooleanArray rendererDisabledFlags; private final SparseBooleanArray rendererDisabledFlags;
private TrackInfo activeTrackInfo;
/** /**
* @param eventHandler A handler to use when delivering events to listeners added via * @param eventHandler A handler to use when delivering events to listeners added via
* {@link #addListener(EventListener)}. * {@link #addListener(EventListener)}.
*/ */
public MappingTrackSelector(Handler eventHandler) { public MappingTrackSelector(Handler eventHandler) {
this.eventHandler = eventHandler; super(eventHandler);
this.listeners = new CopyOnWriteArraySet<>();
selectionOverrides = new SparseArray<>(); selectionOverrides = new SparseArray<>();
rendererDisabledFlags = new SparseBooleanArray(); rendererDisabledFlags = new SparseBooleanArray();
} }
/** /**
* Register a listener to receive events from the selector. The listener's methods will be called
* using the {@link Handler} that was passed to the constructor.
*
* @param listener The listener to register.
*/
public final void addListener(EventListener listener) {
Assertions.checkState(eventHandler != null);
listeners.add(listener);
}
/**
* Unregister a listener. The listener will no longer receive events from the selector.
*
* @param listener The listener to unregister.
*/
public final void removeListener(EventListener listener) {
listeners.remove(listener);
}
/**
* Returns information about the current tracks and track selection for each renderer.
*/
public final TrackInfo getTrackInfo() {
return activeTrackInfo;
}
/**
* Sets whether the renderer at the specified index is disabled. * Sets whether the renderer at the specified index is disabled.
* *
* @param rendererIndex The renderer index. * @param rendererIndex The renderer index.
...@@ -272,13 +224,7 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -272,13 +224,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
// TrackSelector implementation. // TrackSelector implementation.
@Override @Override
public final void onSelectionActivated(Object selectionInfo) { public final TrackSelections<MappedTrackInfo> selectTracks(
activeTrackInfo = (TrackInfo) selectionInfo;
notifyTrackInfoChanged(activeTrackInfo);
}
@Override
public final Pair<TrackSelectionArray, Object> selectTracks(
RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups)
throws ExoPlaybackException { throws ExoPlaybackException {
// Structures into which data will be written during the selection. The extra item at the end // Structures into which data will be written during the selection. The extra item at the end
...@@ -345,11 +291,10 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -345,11 +291,10 @@ public abstract class MappingTrackSelector extends TrackSelector {
} }
// Package up the track information and selections. // Package up the track information and selections.
TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections); MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes,
TrackInfo trackInfo = new TrackInfo(rendererTrackTypes, rendererTrackGroupArrays, rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports,
trackSelections, mixedMimeTypeAdaptationSupport, rendererFormatSupports,
unassociatedTrackGroupArray); unassociatedTrackGroupArray);
return Pair.<TrackSelectionArray, Object>create(trackSelectionArray, trackInfo); return new TrackSelections<>(mappedTrackInfo, trackSelections);
} }
/** /**
...@@ -446,23 +391,10 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -446,23 +391,10 @@ public abstract class MappingTrackSelector extends TrackSelector {
return mixedMimeTypeAdaptationSupport; return mixedMimeTypeAdaptationSupport;
} }
private void notifyTrackInfoChanged(final TrackInfo trackInfo) {
if (eventHandler != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
for (EventListener listener : listeners) {
listener.onTracksChanged(trackInfo);
}
}
});
}
}
/** /**
* Provides track information for each renderer. * Provides track information for each renderer.
*/ */
public static final class TrackInfo { public static final class MappedTrackInfo {
/** /**
* The renderer does not have any associated tracks. * The renderer does not have any associated tracks.
...@@ -477,34 +409,27 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -477,34 +409,27 @@ public abstract class MappingTrackSelector extends TrackSelector {
*/ */
public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 2; public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 2;
/**
* The number of renderers.
*/
public final int rendererCount;
private final int[] rendererTrackTypes; private final int[] rendererTrackTypes;
private final TrackGroupArray[] trackGroups; private final TrackGroupArray[] trackGroups;
private final TrackSelection[] trackSelections;
private final int[] mixedMimeTypeAdaptiveSupport; private final int[] mixedMimeTypeAdaptiveSupport;
private final int[][][] formatSupport; private final int[][][] formatSupport;
private final TrackGroupArray unassociatedTrackGroups; private final TrackGroupArray unassociatedTrackGroups;
private final int rendererCount;
/** /**
* @param rendererTrackTypes The track type supported by each renderer. * @param rendererTrackTypes The track type supported by each renderer.
* @param trackGroups The {@link TrackGroupArray}s for each renderer. * @param trackGroups The {@link TrackGroupArray}s for each renderer.
* @param trackSelections The current {@link TrackSelection}s for each renderer.
* @param mixedMimeTypeAdaptiveSupport The result of * @param mixedMimeTypeAdaptiveSupport The result of
* {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer.
* @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each
* track, indexed by renderer index, group index and track index (in that order). * track, indexed by renderer index, group index and track index (in that order).
* @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer. * @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer.
*/ */
/* package */ TrackInfo(int[] rendererTrackTypes, TrackGroupArray[] trackGroups, /* package */ MappedTrackInfo(int[] rendererTrackTypes,
TrackSelection[] trackSelections, int[] mixedMimeTypeAdaptiveSupport, TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport,
int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) {
this.rendererTrackTypes = rendererTrackTypes; this.rendererTrackTypes = rendererTrackTypes;
this.trackGroups = trackGroups; this.trackGroups = trackGroups;
this.trackSelections = trackSelections;
this.formatSupport = formatSupport; this.formatSupport = formatSupport;
this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport;
this.unassociatedTrackGroups = unassociatedTrackGroups; this.unassociatedTrackGroups = unassociatedTrackGroups;
...@@ -522,16 +447,6 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -522,16 +447,6 @@ public abstract class MappingTrackSelector extends TrackSelector {
} }
/** /**
* Returns the current {@link TrackSelection} for the renderer at a specified index.
*
* @param rendererIndex The renderer index.
* @return The corresponding {@link TrackSelection}, or null if the renderer is disabled.
*/
public TrackSelection getTrackSelection(int rendererIndex) {
return trackSelections[rendererIndex];
}
/**
* Returns the extent to which a renderer can support playback of the tracks associated to it. * Returns the extent to which a renderer can support playback of the tracks associated to it.
* *
* @param rendererIndex The renderer index. * @param rendererIndex The renderer index.
...@@ -657,7 +572,7 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -657,7 +572,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* unplayable. False in all other cases. * unplayable. False in all other cases.
*/ */
public boolean hasOnlyUnplayableTracks(int trackType) { public boolean hasOnlyUnplayableTracks(int trackType) {
int rendererSupport = TrackInfo.RENDERER_SUPPORT_NO_TRACKS; int rendererSupport = RENDERER_SUPPORT_NO_TRACKS;
for (int i = 0; i < rendererCount; i++) { for (int i = 0; i < rendererCount; i++) {
if (rendererTrackTypes[i] == trackType) { if (rendererTrackTypes[i] == trackType) {
rendererSupport = Math.max(rendererSupport, getRendererSupport(i)); rendererSupport = Math.max(rendererSupport, getRendererSupport(i));
......
...@@ -18,12 +18,16 @@ package com.google.android.exoplayer2.trackselection; ...@@ -18,12 +18,16 @@ package com.google.android.exoplayer2.trackselection;
import java.util.Arrays; import java.util.Arrays;
/** /**
* An array of {@link TrackSelection}s generated by a {@link TrackSelector}. * The result of a {@link TrackSelector} operation.
*/ */
public final class TrackSelectionArray { public final class TrackSelections<T> {
/** /**
* The number of selections in the array. Greater than or equal to zero. * Opaque information associated with the result.
*/
public final T info;
/**
* The number of selections in the result. Greater than or equal to zero.
*/ */
public final int length; public final int length;
...@@ -33,9 +37,11 @@ public final class TrackSelectionArray { ...@@ -33,9 +37,11 @@ public final class TrackSelectionArray {
private int hashCode; private int hashCode;
/** /**
* @param info Opaque information associated with the result.
* @param trackSelections The selections. Must not be null, but may contain null elements. * @param trackSelections The selections. Must not be null, but may contain null elements.
*/ */
public TrackSelectionArray(TrackSelection... trackSelections) { public TrackSelections(T info, TrackSelection... trackSelections) {
this.info = info;
this.trackSelections = trackSelections; this.trackSelections = trackSelections;
this.length = trackSelections.length; this.length = trackSelections.length;
} }
...@@ -75,7 +81,7 @@ public final class TrackSelectionArray { ...@@ -75,7 +81,7 @@ public final class TrackSelectionArray {
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
TrackSelectionArray other = (TrackSelectionArray) obj; TrackSelections<?> other = (TrackSelections<?>) obj;
return Arrays.equals(trackSelections, other.trackSelections); return Arrays.equals(trackSelections, other.trackSelections);
} }
......
...@@ -15,15 +15,15 @@ ...@@ -15,15 +15,15 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import android.util.Pair; import android.os.Handler;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import java.util.concurrent.CopyOnWriteArraySet;
/** /** Selects tracks to be consumed by available renderers. */
* Selects tracks to be consumed by available renderers. public abstract class TrackSelector<T> {
*/
public abstract class TrackSelector {
/** /**
* Notified when previous selections by a {@link TrackSelector} are no longer valid. * Notified when previous selections by a {@link TrackSelector} are no longer valid.
...@@ -37,7 +37,55 @@ public abstract class TrackSelector { ...@@ -37,7 +37,55 @@ public abstract class TrackSelector {
} }
/** Listener of {@link TrackSelector} events. */
public interface EventListener<T> {
/**
* Called when the track selections have changed.
*
* @param trackSelections The new track selections.
*/
void onTrackSelectionsChanged(TrackSelections<? extends T> trackSelections);
}
private final Handler eventHandler;
private final CopyOnWriteArraySet<MappingTrackSelector.EventListener<? super T>> listeners;
private InvalidationListener listener; private InvalidationListener listener;
private TrackSelections<T> activeSelections;
/**
* @param eventHandler A handler to use when delivering events to listeners added via {@link
* #addListener(EventListener)}.
*/
public TrackSelector(Handler eventHandler) {
this.eventHandler = Assertions.checkNotNull(eventHandler);
this.listeners = new CopyOnWriteArraySet<>();
}
/**
* Registers a listener to receive events from the selector. The listener's methods will be called
* using the {@link Handler} that was passed to the constructor.
*
* @param listener The listener to register.
*/
public final void addListener(EventListener<? super T> listener) {
listeners.add(listener);
}
/**
* Unregister a listener. The listener will no longer receive events from the selector.
*
* @param listener The listener to unregister.
*/
public final void removeListener(EventListener<? super T> listener) {
listeners.remove(listener);
}
/** Returns the current track selections. */
public final TrackSelections<T> getCurrentSelections() {
return activeSelections;
}
/** /**
* Initializes the selector. * Initializes the selector.
...@@ -49,31 +97,28 @@ public abstract class TrackSelector { ...@@ -49,31 +97,28 @@ public abstract class TrackSelector {
} }
/** /**
* Generates a {@link TrackSelection} for each renderer. * Generates {@link TrackSelections} for the renderers.
* <P>
* The selections are returned in a {@link TrackSelectionArray}, together with an opaque object
* that the selector wishes to receive in an invocation of {@link #onSelectionActivated(Object)}
* should the selection be activated.
* *
* @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which {@link
* {@link TrackSelection}s are to be generated. * TrackSelection}s are to be generated.
* @param trackGroups The available track groups. * @param trackGroups The available track groups.
* @return A {@link TrackSelectionArray} containing a {@link TrackSelection} for each renderer, * @return The track selections.
* together with an opaque object that will be passed to {@link #onSelectionActivated(Object)}
* if the selection is activated.
* @throws ExoPlaybackException If an error occurs selecting tracks. * @throws ExoPlaybackException If an error occurs selecting tracks.
*/ */
public abstract Pair<TrackSelectionArray, Object> selectTracks( public abstract TrackSelections<T> selectTracks(
RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups)
throws ExoPlaybackException; throws ExoPlaybackException;
/** /**
* Called when a selection previously generated by * Called when {@link TrackSelections} previously generated by {@link
* {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} is activated. * #selectTracks(RendererCapabilities[], TrackGroupArray)} are activated.
* *
* @param selectionInfo The opaque object associated with the selection. * @param activeSelections The activated {@link TrackSelections}.
*/ */
public abstract void onSelectionActivated(Object selectionInfo); public final void onSelectionActivated(TrackSelections<T> activeSelections) {
this.activeSelections = activeSelections;
notifyTrackSelectionsChanged(activeSelections);
}
/** /**
* Invalidates all previously generated track selections. * Invalidates all previously generated track selections.
...@@ -84,4 +129,18 @@ public abstract class TrackSelector { ...@@ -84,4 +129,18 @@ public abstract class TrackSelector {
} }
} }
private void notifyTrackSelectionsChanged(final TrackSelections<T> activeSelections) {
if (eventHandler != null) {
eventHandler.post(
new Runnable() {
@Override
public void run() {
for (EventListener<? super T> listener : listeners) {
listener.onTrackSelectionsChanged(activeSelections);
}
}
});
}
}
} }
...@@ -108,7 +108,7 @@ public class PlaybackControlView extends FrameLayout { ...@@ -108,7 +108,7 @@ public class PlaybackControlView extends FrameLayout {
formatter = new Formatter(formatBuilder, Locale.getDefault()); formatter = new Formatter(formatBuilder, Locale.getDefault());
componentListener = new ComponentListener(); componentListener = new ComponentListener();
LayoutInflater.from(context).inflate(R.layout.playback_control_view, this); LayoutInflater.from(context).inflate(R.layout.exo_playback_control_view, this);
time = (TextView) findViewById(R.id.time); time = (TextView) findViewById(R.id.time);
timeCurrent = (TextView) findViewById(R.id.time_current); timeCurrent = (TextView) findViewById(R.id.time_current);
progressBar = (SeekBar) findViewById(R.id.mediacontroller_progress); progressBar = (SeekBar) findViewById(R.id.mediacontroller_progress);
...@@ -240,9 +240,11 @@ public class PlaybackControlView extends FrameLayout { ...@@ -240,9 +240,11 @@ public class PlaybackControlView extends FrameLayout {
return; return;
} }
boolean playing = player != null && player.getPlayWhenReady(); boolean playing = player != null && player.getPlayWhenReady();
playButton.setImageResource(playing ? R.drawable.ic_media_pause : R.drawable.ic_media_play); String contentDescription = getResources().getString(
playButton.setContentDescription( playing ? R.string.exo_controls_pause_description : R.string.exo_controls_play_description);
getResources().getString(playing ? R.string.pause_description : R.string.play_description)); playButton.setContentDescription(contentDescription);
playButton.setImageResource(
playing ? R.drawable.exo_controls_pause : R.drawable.exo_controls_play);
} }
private void updateNavigation() { private void updateNavigation() {
......
...@@ -22,22 +22,18 @@ import android.util.AttributeSet; ...@@ -22,22 +22,18 @@ import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.R;
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.decoder.DecoderCounters;
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 java.util.List; import java.util.List;
/** /**
...@@ -71,8 +67,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -71,8 +67,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SimpleExoPlayerView, 0, 0); R.styleable.SimpleExoPlayerView, 0, 0);
try { try {
useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController);
useController);
useTextureView = a.getBoolean(R.styleable.SimpleExoPlayerView_use_texture_view, useTextureView = a.getBoolean(R.styleable.SimpleExoPlayerView_use_texture_view,
useTextureView); useTextureView);
} finally { } finally {
...@@ -80,7 +75,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -80,7 +75,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
} }
LayoutInflater.from(context).inflate(R.layout.exoplayer_video_view, this); LayoutInflater.from(context).inflate(R.layout.exo_simple_player_view, this);
componentListener = new ComponentListener(); componentListener = new ComponentListener();
layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame); layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
controller = (PlaybackControlView) findViewById(R.id.control); controller = (PlaybackControlView) findViewById(R.id.control);
...@@ -113,7 +108,6 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -113,7 +108,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
this.player.setVideoSurface(null); this.player.setVideoSurface(null);
} }
this.player = player; this.player = player;
if (player != null) { if (player != null) {
if (surfaceView instanceof TextureView) { if (surfaceView instanceof TextureView) {
player.setVideoTextureView((TextureView) surfaceView); player.setVideoTextureView((TextureView) surfaceView);
...@@ -123,6 +117,8 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -123,6 +117,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoListener(componentListener); player.setVideoListener(componentListener);
player.addListener(componentListener); player.addListener(componentListener);
player.setTextOutput(componentListener); player.setTextOutput(componentListener);
} else {
shutterView.setVisibility(VISIBLE);
} }
setUseController(useController); setUseController(useController);
} }
...@@ -234,12 +230,12 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -234,12 +230,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
@Override @Override
public void onRenderedFirstFrame(Surface surface) { public void onRenderedFirstFrame() {
shutterView.setVisibility(GONE); shutterView.setVisibility(GONE);
} }
@Override @Override
public void onVideoDisabled(DecoderCounters counters) { public void onVideoTracksDisabled() {
shutterView.setVisibility(VISIBLE); shutterView.setVisibility(VISIBLE);
} }
......
...@@ -66,9 +66,12 @@ import com.google.android.exoplayer2.util.Util; ...@@ -66,9 +66,12 @@ import com.google.android.exoplayer2.util.Util;
private CharSequence cueText; private CharSequence cueText;
private Alignment cueTextAlignment; private Alignment cueTextAlignment;
private float cueLine; private float cueLine;
@Cue.LineType
private int cueLineType; private int cueLineType;
@Cue.AnchorType
private int cueLineAnchor; private int cueLineAnchor;
private float cuePosition; private float cuePosition;
@Cue.AnchorType
private int cuePositionAnchor; private int cuePositionAnchor;
private float cueSize; private float cueSize;
private boolean applyEmbeddedStyles; private boolean applyEmbeddedStyles;
...@@ -76,6 +79,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -76,6 +79,7 @@ import com.google.android.exoplayer2.util.Util;
private int backgroundColor; private int backgroundColor;
private int windowColor; private int windowColor;
private int edgeColor; private int edgeColor;
@CaptionStyleCompat.EdgeType
private int edgeType; private int edgeType;
private float textSizePx; private float textSizePx;
private float bottomPaddingFraction; private float bottomPaddingFraction;
......
...@@ -16,8 +16,11 @@ ...@@ -16,8 +16,11 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays; import java.util.Arrays;
/** /**
...@@ -26,6 +29,12 @@ import java.util.Arrays; ...@@ -26,6 +29,12 @@ import java.util.Arrays;
public final class DataSpec { public final class DataSpec {
/** /**
* The flags that apply to any request for data.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_ALLOW_GZIP})
public @interface Flags {}
/**
* Permits an underlying network stack to request that the server use gzip compression. * Permits an underlying network stack to request that the server use gzip compression.
* <p> * <p>
* Should not typically be set if the data being requested is already compressed (e.g. most audio * Should not typically be set if the data being requested is already compressed (e.g. most audio
...@@ -69,6 +78,7 @@ public final class DataSpec { ...@@ -69,6 +78,7 @@ public final class DataSpec {
/** /**
* Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag. * Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag.
*/ */
@Flags
public final int flags; public final int flags;
/** /**
...@@ -86,7 +96,7 @@ public final class DataSpec { ...@@ -86,7 +96,7 @@ public final class DataSpec {
* @param uri {@link #uri}. * @param uri {@link #uri}.
* @param flags {@link #flags}. * @param flags {@link #flags}.
*/ */
public DataSpec(Uri uri, int flags) { public DataSpec(Uri uri, @Flags int flags) {
this(uri, 0, C.LENGTH_UNSET, null, flags); this(uri, 0, C.LENGTH_UNSET, null, flags);
} }
...@@ -111,7 +121,7 @@ public final class DataSpec { ...@@ -111,7 +121,7 @@ public final class DataSpec {
* @param key {@link #key}. * @param key {@link #key}.
* @param flags {@link #flags}. * @param flags {@link #flags}.
*/ */
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, int flags) { public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, @Flags int flags) {
this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags); this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags);
} }
...@@ -127,7 +137,7 @@ public final class DataSpec { ...@@ -127,7 +137,7 @@ public final class DataSpec {
* @param flags {@link #flags}. * @param flags {@link #flags}.
*/ */
public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key, public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key,
int flags) { @Flags int flags) {
this(uri, null, absoluteStreamPosition, position, length, key, flags); this(uri, null, absoluteStreamPosition, position, length, key, flags);
} }
...@@ -144,7 +154,7 @@ public final class DataSpec { ...@@ -144,7 +154,7 @@ public final class DataSpec {
* @param flags {@link #flags}. * @param flags {@link #flags}.
*/ */
public DataSpec(Uri uri, byte[] postBody, long absoluteStreamPosition, long position, long length, public DataSpec(Uri uri, byte[] postBody, long absoluteStreamPosition, long position, long length,
String key, int flags) { String key, @Flags int flags) {
Assertions.checkArgument(absoluteStreamPosition >= 0); Assertions.checkArgument(absoluteStreamPosition >= 0);
Assertions.checkArgument(position >= 0); Assertions.checkArgument(position >= 0);
Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET); Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET);
......
...@@ -547,11 +547,8 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -547,11 +547,8 @@ public class DefaultHttpDataSource implements HttpDataSource {
* @throws IOException If an error occurs reading from the source. * @throws IOException If an error occurs reading from the source.
*/ */
private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
readLength = bytesToRead == C.LENGTH_UNSET ? readLength
: (int) Math.min(readLength, bytesToRead - bytesRead);
if (readLength == 0) { if (readLength == 0) {
// We've read all of the requested data. return 0;
return C.RESULT_END_OF_INPUT;
} }
int read = inputStream.read(buffer, offset, readLength); int read = inputStream.read(buffer, offset, readLength);
......
...@@ -15,10 +15,13 @@ ...@@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.support.annotation.IntDef;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Predicate;
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.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -57,10 +60,14 @@ public interface HttpDataSource extends DataSource { ...@@ -57,10 +60,14 @@ public interface HttpDataSource extends DataSource {
*/ */
class HttpDataSourceException extends IOException { class HttpDataSourceException extends IOException {
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE})
public @interface Type {}
public static final int TYPE_OPEN = 1; public static final int TYPE_OPEN = 1;
public static final int TYPE_READ = 2; public static final int TYPE_READ = 2;
public static final int TYPE_CLOSE = 3; public static final int TYPE_CLOSE = 3;
@Type
public final int type; public final int type;
/** /**
...@@ -68,25 +75,26 @@ public interface HttpDataSource extends DataSource { ...@@ -68,25 +75,26 @@ public interface HttpDataSource extends DataSource {
*/ */
public final DataSpec dataSpec; public final DataSpec dataSpec;
public HttpDataSourceException(DataSpec dataSpec, int type) { public HttpDataSourceException(DataSpec dataSpec, @Type int type) {
super(); super();
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
this.type = type; this.type = type;
} }
public HttpDataSourceException(String message, DataSpec dataSpec, int type) { public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) {
super(message); super(message);
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
this.type = type; this.type = type;
} }
public HttpDataSourceException(IOException cause, DataSpec dataSpec, int type) { public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) {
super(cause); super(cause);
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
this.type = type; this.type = type;
} }
public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, int type) { public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec,
@Type int type) {
super(message, cause); super(message, cause);
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
this.type = type; this.type = type;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream.cache; package com.google.android.exoplayer2.upstream.cache;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSink; import com.google.android.exoplayer2.upstream.DataSink;
...@@ -27,6 +28,8 @@ import com.google.android.exoplayer2.upstream.TeeDataSource; ...@@ -27,6 +28,8 @@ import com.google.android.exoplayer2.upstream.TeeDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache * A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache
...@@ -44,6 +47,13 @@ public final class CacheDataSource implements DataSource { ...@@ -44,6 +47,13 @@ public final class CacheDataSource implements DataSource {
public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024; public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024;
/** /**
* Flags controlling the cache's behavior.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR,
FLAG_CACHE_UNBOUNDED_REQUESTS})
public @interface Flags {}
/**
* A flag indicating whether we will block reads if the cache key is locked. If this flag is * A flag indicating whether we will block reads if the cache key is locked. If this flag is
* set, then we will read from upstream if the cache key is locked. * set, then we will read from upstream if the cache key is locked.
*/ */
...@@ -106,7 +116,7 @@ public final class CacheDataSource implements DataSource { ...@@ -106,7 +116,7 @@ public final class CacheDataSource implements DataSource {
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
* reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}.
*/ */
public CacheDataSource(Cache cache, DataSource upstream, int flags) { public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) {
this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE); this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE);
} }
...@@ -123,7 +133,8 @@ public final class CacheDataSource implements DataSource { ...@@ -123,7 +133,8 @@ public final class CacheDataSource implements DataSource {
* exceeds this value, then the data will be fragmented into multiple cache files. The * exceeds this value, then the data will be fragmented into multiple cache files. The
* finer-grained this is the finer-grained the eviction policy can be. * finer-grained this is the finer-grained the eviction policy can be.
*/ */
public CacheDataSource(Cache cache, DataSource upstream, int flags, long maxCacheFileSize) { public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags,
long maxCacheFileSize) {
this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize), this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize),
flags, null); flags, null);
} }
...@@ -142,7 +153,7 @@ public final class CacheDataSource implements DataSource { ...@@ -142,7 +153,7 @@ public final class CacheDataSource implements DataSource {
* @param eventListener An optional {@link EventListener} to receive events. * @param eventListener An optional {@link EventListener} to receive events.
*/ */
public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource,
DataSink cacheWriteDataSink, int flags, EventListener eventListener) { DataSink cacheWriteDataSink, @Flags int flags, EventListener eventListener) {
this.cache = cache; this.cache = cache;
this.cacheReadDataSource = cacheReadDataSource; this.cacheReadDataSource = cacheReadDataSource;
this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0;
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
/** /**
* Defines common MIME types and helper methods. * Defines common MIME types and helper methods.
*/ */
...@@ -61,7 +64,7 @@ public final class MimeTypes { ...@@ -61,7 +64,7 @@ public final class MimeTypes {
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
public static final String APPLICATION_EIA608 = BASE_TYPE_APPLICATION + "/eia-608"; public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip"; public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip";
public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml";
public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
...@@ -192,6 +195,44 @@ public final class MimeTypes { ...@@ -192,6 +195,44 @@ public final class MimeTypes {
} }
/** /**
* Returns the {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type.
* {@link C#TRACK_TYPE_UNKNOWN} if the mime type is not known or the mapping cannot be
* established.
*
* @param mimeType The mimeType.
* @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type.
*/
public static int getTrackType(String mimeType) {
if (TextUtils.isEmpty(mimeType)) {
return C.TRACK_TYPE_UNKNOWN;
} else if (isAudio(mimeType)) {
return C.TRACK_TYPE_AUDIO;
} else if (isVideo(mimeType)) {
return C.TRACK_TYPE_VIDEO;
} else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType)
|| APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType)
|| APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType)
|| APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType)
|| APPLICATION_PGS.equals(mimeType)) {
return C.TRACK_TYPE_TEXT;
} else if (APPLICATION_ID3.equals(mimeType)) {
return C.TRACK_TYPE_METADATA;
} else {
return C.TRACK_TYPE_UNKNOWN;
}
}
/**
* Equivalent to {@code getTrackType(getMediaMimeType(codec))}.
*
* @param codec The codec.
* @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified codec.
*/
public static int getTrackTypeOfCodec(String codec) {
return getTrackType(getMediaMimeType(codec));
}
/**
* Returns the top-level type of {@code mimeType}. * Returns the top-level type of {@code mimeType}.
* *
* @param mimeType The mimeType whose top-level type is required. * @param mimeType The mimeType whose top-level type is required.
......
...@@ -35,17 +35,17 @@ public final class ParsableByteArray { ...@@ -35,17 +35,17 @@ public final class ParsableByteArray {
public ParsableByteArray() {} public ParsableByteArray() {}
/** /**
* Creates a new instance with {@code length} bytes. * Creates a new instance with {@code limit} bytes and sets the limit.
* *
* @param length The length of the array. * @param limit The limit to set.
*/ */
public ParsableByteArray(int length) { public ParsableByteArray(int limit) {
this.data = new byte[length]; this.data = new byte[limit];
limit = data.length; this.limit = limit;
} }
/** /**
* Creates a new instance wrapping {@code data}. * Creates a new instance wrapping {@code data}, and sets the limit to {@code data.length}.
* *
* @param data The array to wrap. * @param data The array to wrap.
*/ */
...@@ -58,7 +58,7 @@ public final class ParsableByteArray { ...@@ -58,7 +58,7 @@ public final class ParsableByteArray {
* Creates a new instance that wraps an existing array. * Creates a new instance that wraps an existing array.
* *
* @param data The data to wrap. * @param data The data to wrap.
* @param limit The limit. * @param limit The limit to set.
*/ */
public ParsableByteArray(byte[] data, int limit) { public ParsableByteArray(byte[] data, int limit) {
this.data = data; this.data = data;
...@@ -79,7 +79,7 @@ public final class ParsableByteArray { ...@@ -79,7 +79,7 @@ public final class ParsableByteArray {
* Updates the instance to wrap {@code data}, and resets the position to zero. * Updates the instance to wrap {@code data}, and resets the position to zero.
* *
* @param data The array to wrap. * @param data The array to wrap.
* @param limit The limit. * @param limit The limit to set.
*/ */
public void reset(byte[] data, int limit) { public void reset(byte[] data, int limit) {
this.data = data; this.data = data;
...@@ -195,6 +195,13 @@ public final class ParsableByteArray { ...@@ -195,6 +195,13 @@ public final class ParsableByteArray {
} }
/** /**
* Peeks at the next byte as an unsigned value.
*/
public int peekUnsignedByte() {
return (data[position] & 0xFF);
}
/**
* Reads the next byte as an unsigned value. * Reads the next byte as an unsigned value.
*/ */
public int readUnsignedByte() { public int readUnsignedByte() {
......
...@@ -88,27 +88,6 @@ public final class Util { ...@@ -88,27 +88,6 @@ public final class Util {
*/ */
public static final String MODEL = Build.MODEL; public static final String MODEL = Build.MODEL;
/**
* Value returned by {@link #inferContentType(String)} for DASH manifests.
*/
public static final int TYPE_DASH = 0;
/**
* Value returned by {@link #inferContentType(String)} for Smooth Streaming manifests.
*/
public static final int TYPE_SS = 1;
/**
* Value returned by {@link #inferContentType(String)} for HLS manifests.
*/
public static final int TYPE_HLS = 2;
/**
* Value returned by {@link #inferContentType(String)} for files other than DASH, HLS or Smooth
* Streaming manifests.
*/
public static final int TYPE_OTHER = 3;
private static final String TAG = "Util"; private static final String TAG = "Util";
private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile(
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
...@@ -712,6 +691,7 @@ public final class Util { ...@@ -712,6 +691,7 @@ public final class Util {
* {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then
* {@link C#ENCODING_INVALID} is returned. * {@link C#ENCODING_INVALID} is returned.
*/ */
@C.PcmEncoding
public static int getPcmEncoding(int bitDepth) { public static int getPcmEncoding(int bitDepth) {
switch (bitDepth) { switch (bitDepth) {
case 8: case 8:
...@@ -731,19 +711,20 @@ public final class Util { ...@@ -731,19 +711,20 @@ public final class Util {
* Makes a best guess to infer the type from a file name. * Makes a best guess to infer the type from a file name.
* *
* @param fileName Name of the file. It can include the path of the file. * @param fileName Name of the file. It can include the path of the file.
* @return One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link #TYPE_HLS} or {@link #TYPE_OTHER}. * @return The content type.
*/ */
@C.ContentType
public static int inferContentType(String fileName) { public static int inferContentType(String fileName) {
if (fileName == null) { if (fileName == null) {
return TYPE_OTHER; return C.TYPE_OTHER;
} else if (fileName.endsWith(".mpd")) { } else if (fileName.endsWith(".mpd")) {
return TYPE_DASH; return C.TYPE_DASH;
} else if (fileName.endsWith(".ism") || fileName.endsWith(".isml")) { } else if (fileName.endsWith(".ism") || fileName.endsWith(".isml")) {
return TYPE_SS; return C.TYPE_SS;
} else if (fileName.endsWith(".m3u8")) { } else if (fileName.endsWith(".m3u8")) {
return TYPE_HLS; return C.TYPE_HLS;
} else { } else {
return TYPE_OTHER; return C.TYPE_OTHER;
} }
} }
......
...@@ -29,23 +29,23 @@ ...@@ -29,23 +29,23 @@
android:orientation="horizontal"> android:orientation="horizontal">
<ImageButton android:id="@+id/prev" <ImageButton android:id="@+id/prev"
android:contentDescription="@string/prev_description" android:contentDescription="@string/exo_controls_previous_description"
style="@style/MediaButton.Previous"/> style="@style/ExoMediaButton.Previous"/>
<ImageButton android:id="@+id/rew" <ImageButton android:id="@+id/rew"
android:contentDescription="@string/rew_description" android:contentDescription="@string/exo_controls_rewind_description"
style="@style/MediaButton.Rew"/> style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@+id/play" <ImageButton android:id="@+id/play"
style="@style/MediaButton"/> style="@style/ExoMediaButton"/>
<ImageButton android:id="@+id/ffwd" <ImageButton android:id="@+id/ffwd"
android:contentDescription="@string/ffw_description" android:contentDescription="@string/exo_controls_fastforward_description"
style="@style/MediaButton.Ffwd"/> style="@style/ExoMediaButton.FastForward"/>
<ImageButton android:id="@+id/next" <ImageButton android:id="@+id/next"
android:contentDescription="@string/prev_description" android:contentDescription="@string/exo_controls_previous_description"
style="@style/MediaButton.Next"/> style="@style/ExoMediaButton.Next"/>
</LinearLayout> </LinearLayout>
......
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Vorige snit"</string> <string name="exo_controls_previous_description">"Vorige snit"</string>
<string name="next_description">"Volgende snit"</string> <string name="exo_controls_next_description">"Volgende snit"</string>
<string name="pause_description">"Wag"</string> <string name="exo_controls_pause_description">"Wag"</string>
<string name="play_description">"Speel"</string> <string name="exo_controls_play_description">"Speel"</string>
<string name="stop_description">"Stop"</string> <string name="exo_controls_stop_description">"Stop"</string>
<string name="rew_description">"Spoel terug"</string> <string name="exo_controls_rewind_description">"Spoel terug"</string>
<string name="ffw_description">"Vinnig vorentoe"</string> <string name="exo_controls_fastforward_description">"Vinnig vorentoe"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"ቀዳሚ ትራክ"</string> <string name="exo_controls_previous_description">"ቀዳሚ ትራክ"</string>
<string name="next_description">"ቀጣይ ትራክ"</string> <string name="exo_controls_next_description">"ቀጣይ ትራክ"</string>
<string name="pause_description">"ለአፍታ አቁም"</string> <string name="exo_controls_pause_description">"ለአፍታ አቁም"</string>
<string name="play_description">"አጫውት"</string> <string name="exo_controls_play_description">"አጫውት"</string>
<string name="stop_description">"አቁም"</string> <string name="exo_controls_stop_description">"አቁም"</string>
<string name="rew_description">"ወደኋላ አጠንጥን"</string> <string name="exo_controls_rewind_description">"ወደኋላ አጠንጥን"</string>
<string name="ffw_description">"በፍጥነት አሳልፍ"</string> <string name="exo_controls_fastforward_description">"በፍጥነት አሳልፍ"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"المقطع الصوتي السابق"</string> <string name="exo_controls_previous_description">"المقطع الصوتي السابق"</string>
<string name="next_description">"المقطع الصوتي التالي"</string> <string name="exo_controls_next_description">"المقطع الصوتي التالي"</string>
<string name="pause_description">"إيقاف مؤقت"</string> <string name="exo_controls_pause_description">"إيقاف مؤقت"</string>
<string name="play_description">"تشغيل"</string> <string name="exo_controls_play_description">"تشغيل"</string>
<string name="stop_description">"إيقاف"</string> <string name="exo_controls_stop_description">"إيقاف"</string>
<string name="rew_description">"إرجاع"</string> <string name="exo_controls_rewind_description">"إرجاع"</string>
<string name="ffw_description">"تقديم سريع"</string> <string name="exo_controls_fastforward_description">"تقديم سريع"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Öncəki trek"</string> <string name="exo_controls_previous_description">"Öncəki trek"</string>
<string name="next_description">"Növbəti trek"</string> <string name="exo_controls_next_description">"Növbəti trek"</string>
<string name="pause_description">"Pauza"</string> <string name="exo_controls_pause_description">"Pauza"</string>
<string name="play_description">"Oyun"</string> <string name="exo_controls_play_description">"Oyun"</string>
<string name="stop_description">"Dayandır"</string> <string name="exo_controls_stop_description">"Dayandır"</string>
<string name="rew_description">"Geri sarıma"</string> <string name="exo_controls_rewind_description">"Geri sarıma"</string>
<string name="ffw_description">"Sürətlə irəli"</string> <string name="exo_controls_fastforward_description">"Sürətlə irəli"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Prethodna pesma"</string> <string name="exo_controls_previous_description">"Prethodna pesma"</string>
<string name="next_description">"Sledeća pesma"</string> <string name="exo_controls_next_description">"Sledeća pesma"</string>
<string name="pause_description">"Pauza"</string> <string name="exo_controls_pause_description">"Pauza"</string>
<string name="play_description">"Pusti"</string> <string name="exo_controls_play_description">"Pusti"</string>
<string name="stop_description">"Zaustavi"</string> <string name="exo_controls_stop_description">"Zaustavi"</string>
<string name="rew_description">"Premotaj unazad"</string> <string name="exo_controls_rewind_description">"Premotaj unazad"</string>
<string name="ffw_description">"Premotaj unapred"</string> <string name="exo_controls_fastforward_description">"Premotaj unapred"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Папярэдні трэк"</string> <string name="exo_controls_previous_description">"Папярэдні трэк"</string>
<string name="next_description">"Наступны трэк"</string> <string name="exo_controls_next_description">"Наступны трэк"</string>
<string name="pause_description">"Прыпыніць"</string> <string name="exo_controls_pause_description">"Прыпыніць"</string>
<string name="play_description">"Прайграць"</string> <string name="exo_controls_play_description">"Прайграць"</string>
<string name="stop_description">"Спыніць"</string> <string name="exo_controls_stop_description">"Спыніць"</string>
<string name="rew_description">"Перамотка назад"</string> <string name="exo_controls_rewind_description">"Перамотка назад"</string>
<string name="ffw_description">"Перамотка ўперад"</string> <string name="exo_controls_fastforward_description">"Перамотка ўперад"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Предишен запис"</string> <string name="exo_controls_previous_description">"Предишен запис"</string>
<string name="next_description">"Следващ запис"</string> <string name="exo_controls_next_description">"Следващ запис"</string>
<string name="pause_description">"Пауза"</string> <string name="exo_controls_pause_description">"Пауза"</string>
<string name="play_description">"Пускане"</string> <string name="exo_controls_play_description">"Пускане"</string>
<string name="stop_description">"Спиране"</string> <string name="exo_controls_stop_description">"Спиране"</string>
<string name="rew_description">"Превъртане назад"</string> <string name="exo_controls_rewind_description">"Превъртане назад"</string>
<string name="ffw_description">"Превъртане напред"</string> <string name="exo_controls_fastforward_description">"Превъртане напред"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"পূর্ববর্তী ট্র্যাক"</string> <string name="exo_controls_previous_description">"পূর্ববর্তী ট্র্যাক"</string>
<string name="next_description">"পরবর্তী ট্র্যাক"</string> <string name="exo_controls_next_description">"পরবর্তী ট্র্যাক"</string>
<string name="pause_description">"বিরাম দিন"</string> <string name="exo_controls_pause_description">"বিরাম দিন"</string>
<string name="play_description">"প্লে করুন"</string> <string name="exo_controls_play_description">"প্লে করুন"</string>
<string name="stop_description">"থামান"</string> <string name="exo_controls_stop_description">"থামান"</string>
<string name="rew_description">"গুটিয়ে নিন"</string> <string name="exo_controls_rewind_description">"গুটিয়ে নিন"</string>
<string name="ffw_description">"দ্রুত সামনে এগোন"</string> <string name="exo_controls_fastforward_description">"দ্রুত সামনে এগোন"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Prethodna numera"</string> <string name="exo_controls_previous_description">"Prethodna numera"</string>
<string name="next_description">"Sljedeća numera"</string> <string name="exo_controls_next_description">"Sljedeća numera"</string>
<string name="pause_description">"Pauziraj"</string> <string name="exo_controls_pause_description">"Pauziraj"</string>
<string name="play_description">"Reproduciraj"</string> <string name="exo_controls_play_description">"Reproduciraj"</string>
<string name="stop_description">"Zaustavi"</string> <string name="exo_controls_stop_description">"Zaustavi"</string>
<string name="rew_description">"Premotaj"</string> <string name="exo_controls_rewind_description">"Premotaj"</string>
<string name="ffw_description">"Ubrzaj"</string> <string name="exo_controls_fastforward_description">"Ubrzaj"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Ruta anterior"</string> <string name="exo_controls_previous_description">"Ruta anterior"</string>
<string name="next_description">"Ruta següent"</string> <string name="exo_controls_next_description">"Ruta següent"</string>
<string name="pause_description">"Posa en pausa"</string> <string name="exo_controls_pause_description">"Posa en pausa"</string>
<string name="play_description">"Reprodueix"</string> <string name="exo_controls_play_description">"Reprodueix"</string>
<string name="stop_description">"Atura"</string> <string name="exo_controls_stop_description">"Atura"</string>
<string name="rew_description">"Rebobina"</string> <string name="exo_controls_rewind_description">"Rebobina"</string>
<string name="ffw_description">"Avança ràpidament"</string> <string name="exo_controls_fastforward_description">"Avança ràpidament"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Předchozí skladba"</string> <string name="exo_controls_previous_description">"Předchozí skladba"</string>
<string name="next_description">"Další skladba"</string> <string name="exo_controls_next_description">"Další skladba"</string>
<string name="pause_description">"Pozastavit"</string> <string name="exo_controls_pause_description">"Pozastavit"</string>
<string name="play_description">"Přehrát"</string> <string name="exo_controls_play_description">"Přehrát"</string>
<string name="stop_description">"Zastavit"</string> <string name="exo_controls_stop_description">"Zastavit"</string>
<string name="rew_description">"Přetočit zpět"</string> <string name="exo_controls_rewind_description">"Přetočit zpět"</string>
<string name="ffw_description">"Přetočit vpřed"</string> <string name="exo_controls_fastforward_description">"Přetočit vpřed"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Forrige nummer"</string> <string name="exo_controls_previous_description">"Forrige nummer"</string>
<string name="next_description">"Næste nummer"</string> <string name="exo_controls_next_description">"Næste nummer"</string>
<string name="pause_description">"Pause"</string> <string name="exo_controls_pause_description">"Pause"</string>
<string name="play_description">"Afspil"</string> <string name="exo_controls_play_description">"Afspil"</string>
<string name="stop_description">"Stop"</string> <string name="exo_controls_stop_description">"Stop"</string>
<string name="rew_description">"Spol tilbage"</string> <string name="exo_controls_rewind_description">"Spol tilbage"</string>
<string name="ffw_description">"Spol frem"</string> <string name="exo_controls_fastforward_description">"Spol frem"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Vorheriger Titel"</string> <string name="exo_controls_previous_description">"Vorheriger Titel"</string>
<string name="next_description">"Nächster Titel"</string> <string name="exo_controls_next_description">"Nächster Titel"</string>
<string name="pause_description">"Pausieren"</string> <string name="exo_controls_pause_description">"Pausieren"</string>
<string name="play_description">"Wiedergabe"</string> <string name="exo_controls_play_description">"Wiedergabe"</string>
<string name="stop_description">"Beenden"</string> <string name="exo_controls_stop_description">"Beenden"</string>
<string name="rew_description">"Zurückspulen"</string> <string name="exo_controls_rewind_description">"Zurückspulen"</string>
<string name="ffw_description">"Vorspulen"</string> <string name="exo_controls_fastforward_description">"Vorspulen"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Προηγούμενο κομμάτι"</string> <string name="exo_controls_previous_description">"Προηγούμενο κομμάτι"</string>
<string name="next_description">"Επόμενο κομμάτι"</string> <string name="exo_controls_next_description">"Επόμενο κομμάτι"</string>
<string name="pause_description">"Παύση"</string> <string name="exo_controls_pause_description">"Παύση"</string>
<string name="play_description">"Αναπαραγωγή"</string> <string name="exo_controls_play_description">"Αναπαραγωγή"</string>
<string name="stop_description">"Διακοπή"</string> <string name="exo_controls_stop_description">"Διακοπή"</string>
<string name="rew_description">"Επαναφορά"</string> <string name="exo_controls_rewind_description">"Επαναφορά"</string>
<string name="ffw_description">"Γρήγορη προώθηση"</string> <string name="exo_controls_fastforward_description">"Γρήγορη προώθηση"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Previous track"</string> <string name="exo_controls_previous_description">"Previous track"</string>
<string name="next_description">"Next track"</string> <string name="exo_controls_next_description">"Next track"</string>
<string name="pause_description">"Pause"</string> <string name="exo_controls_pause_description">"Pause"</string>
<string name="play_description">"Play"</string> <string name="exo_controls_play_description">"Play"</string>
<string name="stop_description">"Stop"</string> <string name="exo_controls_stop_description">"Stop"</string>
<string name="rew_description">"Rewind"</string> <string name="exo_controls_rewind_description">"Rewind"</string>
<string name="ffw_description">"Fast-forward"</string> <string name="exo_controls_fastforward_description">"Fast-forward"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Previous track"</string> <string name="exo_controls_previous_description">"Previous track"</string>
<string name="next_description">"Next track"</string> <string name="exo_controls_next_description">"Next track"</string>
<string name="pause_description">"Pause"</string> <string name="exo_controls_pause_description">"Pause"</string>
<string name="play_description">"Play"</string> <string name="exo_controls_play_description">"Play"</string>
<string name="stop_description">"Stop"</string> <string name="exo_controls_stop_description">"Stop"</string>
<string name="rew_description">"Rewind"</string> <string name="exo_controls_rewind_description">"Rewind"</string>
<string name="ffw_description">"Fast-forward"</string> <string name="exo_controls_fastforward_description">"Fast-forward"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Previous track"</string> <string name="exo_controls_previous_description">"Previous track"</string>
<string name="next_description">"Next track"</string> <string name="exo_controls_next_description">"Next track"</string>
<string name="pause_description">"Pause"</string> <string name="exo_controls_pause_description">"Pause"</string>
<string name="play_description">"Play"</string> <string name="exo_controls_play_description">"Play"</string>
<string name="stop_description">"Stop"</string> <string name="exo_controls_stop_description">"Stop"</string>
<string name="rew_description">"Rewind"</string> <string name="exo_controls_rewind_description">"Rewind"</string>
<string name="ffw_description">"Fast-forward"</string> <string name="exo_controls_fastforward_description">"Fast-forward"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Pista anterior"</string> <string name="exo_controls_previous_description">"Pista anterior"</string>
<string name="next_description">"Siguiente pista"</string> <string name="exo_controls_next_description">"Siguiente pista"</string>
<string name="pause_description">"Pausar"</string> <string name="exo_controls_pause_description">"Pausar"</string>
<string name="play_description">"Reproducir"</string> <string name="exo_controls_play_description">"Reproducir"</string>
<string name="stop_description">"Detener"</string> <string name="exo_controls_stop_description">"Detener"</string>
<string name="rew_description">"Retroceder"</string> <string name="exo_controls_rewind_description">"Retroceder"</string>
<string name="ffw_description">"Avanzar"</string> <string name="exo_controls_fastforward_description">"Avanzar"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Canción anterior"</string> <string name="exo_controls_previous_description">"Canción anterior"</string>
<string name="next_description">"Siguiente canción"</string> <string name="exo_controls_next_description">"Siguiente canción"</string>
<string name="pause_description">"Pausar"</string> <string name="exo_controls_pause_description">"Pausar"</string>
<string name="play_description">"Reproducir"</string> <string name="exo_controls_play_description">"Reproducir"</string>
<string name="stop_description">"Detener"</string> <string name="exo_controls_stop_description">"Detener"</string>
<string name="rew_description">"Rebobinar"</string> <string name="exo_controls_rewind_description">"Rebobinar"</string>
<string name="ffw_description">"Avance rápido"</string> <string name="exo_controls_fastforward_description">"Avance rápido"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Eelmine lugu"</string> <string name="exo_controls_previous_description">"Eelmine lugu"</string>
<string name="next_description">"Järgmine lugu"</string> <string name="exo_controls_next_description">"Järgmine lugu"</string>
<string name="pause_description">"Peata"</string> <string name="exo_controls_pause_description">"Peata"</string>
<string name="play_description">"Esita"</string> <string name="exo_controls_play_description">"Esita"</string>
<string name="stop_description">"Peata"</string> <string name="exo_controls_stop_description">"Peata"</string>
<string name="rew_description">"Keri tagasi"</string> <string name="exo_controls_rewind_description">"Keri tagasi"</string>
<string name="ffw_description">"Keri edasi"</string> <string name="exo_controls_fastforward_description">"Keri edasi"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Aurreko pista"</string> <string name="exo_controls_previous_description">"Aurreko pista"</string>
<string name="next_description">"Hurrengo pista"</string> <string name="exo_controls_next_description">"Hurrengo pista"</string>
<string name="pause_description">"Pausatu"</string> <string name="exo_controls_pause_description">"Pausatu"</string>
<string name="play_description">"Erreproduzitu"</string> <string name="exo_controls_play_description">"Erreproduzitu"</string>
<string name="stop_description">"Gelditu"</string> <string name="exo_controls_stop_description">"Gelditu"</string>
<string name="rew_description">"Atzeratu"</string> <string name="exo_controls_rewind_description">"Atzeratu"</string>
<string name="ffw_description">"Aurreratu"</string> <string name="exo_controls_fastforward_description">"Aurreratu"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"آهنگ قبلی"</string> <string name="exo_controls_previous_description">"آهنگ قبلی"</string>
<string name="next_description">"آهنگ بعدی"</string> <string name="exo_controls_next_description">"آهنگ بعدی"</string>
<string name="pause_description">"مکث"</string> <string name="exo_controls_pause_description">"مکث"</string>
<string name="play_description">"پخش"</string> <string name="exo_controls_play_description">"پخش"</string>
<string name="stop_description">"توقف"</string> <string name="exo_controls_stop_description">"توقف"</string>
<string name="rew_description">"عقب بردن"</string> <string name="exo_controls_rewind_description">"عقب بردن"</string>
<string name="ffw_description">"جلو بردن سریع"</string> <string name="exo_controls_fastforward_description">"جلو بردن سریع"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Edellinen raita"</string> <string name="exo_controls_previous_description">"Edellinen raita"</string>
<string name="next_description">"Seuraava raita"</string> <string name="exo_controls_next_description">"Seuraava raita"</string>
<string name="pause_description">"Tauko"</string> <string name="exo_controls_pause_description">"Tauko"</string>
<string name="play_description">"Toista"</string> <string name="exo_controls_play_description">"Toista"</string>
<string name="stop_description">"Seis"</string> <string name="exo_controls_stop_description">"Seis"</string>
<string name="rew_description">"Kelaa taakse"</string> <string name="exo_controls_rewind_description">"Kelaa taakse"</string>
<string name="ffw_description">"Kelaa eteen"</string> <string name="exo_controls_fastforward_description">"Kelaa eteen"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Chanson précédente"</string> <string name="exo_controls_previous_description">"Chanson précédente"</string>
<string name="next_description">"Chanson suivante"</string> <string name="exo_controls_next_description">"Chanson suivante"</string>
<string name="pause_description">"Pause"</string> <string name="exo_controls_pause_description">"Pause"</string>
<string name="play_description">"Lecture"</string> <string name="exo_controls_play_description">"Lecture"</string>
<string name="stop_description">"Arrêter"</string> <string name="exo_controls_stop_description">"Arrêter"</string>
<string name="rew_description">"Reculer"</string> <string name="exo_controls_rewind_description">"Reculer"</string>
<string name="ffw_description">"Avance rapide"</string> <string name="exo_controls_fastforward_description">"Avance rapide"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Piste précédente"</string> <string name="exo_controls_previous_description">"Piste précédente"</string>
<string name="next_description">"Piste suivante"</string> <string name="exo_controls_next_description">"Piste suivante"</string>
<string name="pause_description">"Interrompre"</string> <string name="exo_controls_pause_description">"Interrompre"</string>
<string name="play_description">"Lire"</string> <string name="exo_controls_play_description">"Lire"</string>
<string name="stop_description">"Arrêter"</string> <string name="exo_controls_stop_description">"Arrêter"</string>
<string name="rew_description">"Retour arrière"</string> <string name="exo_controls_rewind_description">"Retour arrière"</string>
<string name="ffw_description">"Avance rapide"</string> <string name="exo_controls_fastforward_description">"Avance rapide"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Pista anterior"</string> <string name="exo_controls_previous_description">"Pista anterior"</string>
<string name="next_description">"Seguinte pista"</string> <string name="exo_controls_next_description">"Seguinte pista"</string>
<string name="pause_description">"Pausar"</string> <string name="exo_controls_pause_description">"Pausar"</string>
<string name="play_description">"Reproducir"</string> <string name="exo_controls_play_description">"Reproducir"</string>
<string name="stop_description">"Deter"</string> <string name="exo_controls_stop_description">"Deter"</string>
<string name="rew_description">"Rebobinar"</string> <string name="exo_controls_rewind_description">"Rebobinar"</string>
<string name="ffw_description">"Avance rápido"</string> <string name="exo_controls_fastforward_description">"Avance rápido"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"પહેલાનો ટ્રૅક"</string> <string name="exo_controls_previous_description">"પહેલાનો ટ્રૅક"</string>
<string name="next_description">"આગલો ટ્રૅક"</string> <string name="exo_controls_next_description">"આગલો ટ્રૅક"</string>
<string name="pause_description">"થોભો"</string> <string name="exo_controls_pause_description">"થોભો"</string>
<string name="play_description">"ચલાવો"</string> <string name="exo_controls_play_description">"ચલાવો"</string>
<string name="stop_description">"રોકો"</string> <string name="exo_controls_stop_description">"રોકો"</string>
<string name="rew_description">"રીવાઇન્ડ કરો"</string> <string name="exo_controls_rewind_description">"રીવાઇન્ડ કરો"</string>
<string name="ffw_description">"ઝડપી ફોરવર્ડ કરો"</string> <string name="exo_controls_fastforward_description">"ઝડપી ફોરવર્ડ કરો"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"पिछला ट्रैक"</string> <string name="exo_controls_previous_description">"पिछला ट्रैक"</string>
<string name="next_description">"अगला ट्रैक"</string> <string name="exo_controls_next_description">"अगला ट्रैक"</string>
<string name="pause_description">"रोकें"</string> <string name="exo_controls_pause_description">"रोकें"</string>
<string name="play_description">"चलाएं"</string> <string name="exo_controls_play_description">"चलाएं"</string>
<string name="stop_description">"बंद करें"</string> <string name="exo_controls_stop_description">"बंद करें"</string>
<string name="rew_description">"रिवाइंड करें"</string> <string name="exo_controls_rewind_description">"रिवाइंड करें"</string>
<string name="ffw_description">"फ़ास्ट फ़ॉरवर्ड"</string> <string name="exo_controls_fastforward_description">"फ़ास्ट फ़ॉरवर्ड"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Prethodna pjesma"</string> <string name="exo_controls_previous_description">"Prethodna pjesma"</string>
<string name="next_description">"Sljedeća pjesma"</string> <string name="exo_controls_next_description">"Sljedeća pjesma"</string>
<string name="pause_description">"Pauziraj"</string> <string name="exo_controls_pause_description">"Pauziraj"</string>
<string name="play_description">"Reproduciraj"</string> <string name="exo_controls_play_description">"Reproduciraj"</string>
<string name="stop_description">"Zaustavi"</string> <string name="exo_controls_stop_description">"Zaustavi"</string>
<string name="rew_description">"Unatrag"</string> <string name="exo_controls_rewind_description">"Unatrag"</string>
<string name="ffw_description">"Brzo unaprijed"</string> <string name="exo_controls_fastforward_description">"Brzo unaprijed"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Előző szám"</string> <string name="exo_controls_previous_description">"Előző szám"</string>
<string name="next_description">"Következő szám"</string> <string name="exo_controls_next_description">"Következő szám"</string>
<string name="pause_description">"Szünet"</string> <string name="exo_controls_pause_description">"Szünet"</string>
<string name="play_description">"Lejátszás"</string> <string name="exo_controls_play_description">"Lejátszás"</string>
<string name="stop_description">"Leállítás"</string> <string name="exo_controls_stop_description">"Leállítás"</string>
<string name="rew_description">"Visszatekerés"</string> <string name="exo_controls_rewind_description">"Visszatekerés"</string>
<string name="ffw_description">"Előretekerés"</string> <string name="exo_controls_fastforward_description">"Előretekerés"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Նախորդը"</string> <string name="exo_controls_previous_description">"Նախորդը"</string>
<string name="next_description">"Հաջորդը"</string> <string name="exo_controls_next_description">"Հաջորդը"</string>
<string name="pause_description">"Դադարեցնել"</string> <string name="exo_controls_pause_description">"Դադարեցնել"</string>
<string name="play_description">"Նվագարկել"</string> <string name="exo_controls_play_description">"Նվագարկել"</string>
<string name="stop_description">"Դադարեցնել"</string> <string name="exo_controls_stop_description">"Դադարեցնել"</string>
<string name="rew_description">"Հետ փաթաթել"</string> <string name="exo_controls_rewind_description">"Հետ փաթաթել"</string>
<string name="ffw_description">"Արագ առաջ անցնել"</string> <string name="exo_controls_fastforward_description">"Արագ առաջ անցնել"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Lagu sebelumnya"</string> <string name="exo_controls_previous_description">"Lagu sebelumnya"</string>
<string name="next_description">"Lagu berikutnya"</string> <string name="exo_controls_next_description">"Lagu berikutnya"</string>
<string name="pause_description">"Jeda"</string> <string name="exo_controls_pause_description">"Jeda"</string>
<string name="play_description">"Putar"</string> <string name="exo_controls_play_description">"Putar"</string>
<string name="stop_description">"Berhenti"</string> <string name="exo_controls_stop_description">"Berhenti"</string>
<string name="rew_description">"Putar Ulang"</string> <string name="exo_controls_rewind_description">"Putar Ulang"</string>
<string name="ffw_description">"Maju cepat"</string> <string name="exo_controls_fastforward_description">"Maju cepat"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Fyrra lag"</string> <string name="exo_controls_previous_description">"Fyrra lag"</string>
<string name="next_description">"Næsta lag"</string> <string name="exo_controls_next_description">"Næsta lag"</string>
<string name="pause_description">"Hlé"</string> <string name="exo_controls_pause_description">"Hlé"</string>
<string name="play_description">"Spila"</string> <string name="exo_controls_play_description">"Spila"</string>
<string name="stop_description">"Stöðva"</string> <string name="exo_controls_stop_description">"Stöðva"</string>
<string name="rew_description">"Spóla til baka"</string> <string name="exo_controls_rewind_description">"Spóla til baka"</string>
<string name="ffw_description">"Spóla áfram"</string> <string name="exo_controls_fastforward_description">"Spóla áfram"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Traccia precedente"</string> <string name="exo_controls_previous_description">"Traccia precedente"</string>
<string name="next_description">"Traccia successiva"</string> <string name="exo_controls_next_description">"Traccia successiva"</string>
<string name="pause_description">"Metti in pausa"</string> <string name="exo_controls_pause_description">"Metti in pausa"</string>
<string name="play_description">"Riproduci"</string> <string name="exo_controls_play_description">"Riproduci"</string>
<string name="stop_description">"Interrompi"</string> <string name="exo_controls_stop_description">"Interrompi"</string>
<string name="rew_description">"Riavvolgi"</string> <string name="exo_controls_rewind_description">"Riavvolgi"</string>
<string name="ffw_description">"Avanti veloce"</string> <string name="exo_controls_fastforward_description">"Avanti veloce"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"הרצועה הקודמת"</string> <string name="exo_controls_previous_description">"הרצועה הקודמת"</string>
<string name="next_description">"הרצועה הבאה"</string> <string name="exo_controls_next_description">"הרצועה הבאה"</string>
<string name="pause_description">"השהה"</string> <string name="exo_controls_pause_description">"השהה"</string>
<string name="play_description">"הפעל"</string> <string name="exo_controls_play_description">"הפעל"</string>
<string name="stop_description">"הפסק"</string> <string name="exo_controls_stop_description">"הפסק"</string>
<string name="rew_description">"הרץ אחורה"</string> <string name="exo_controls_rewind_description">"הרץ אחורה"</string>
<string name="ffw_description">"הרץ קדימה"</string> <string name="exo_controls_fastforward_description">"הרץ קדימה"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"前のトラック"</string> <string name="exo_controls_previous_description">"前のトラック"</string>
<string name="next_description">"次のトラック"</string> <string name="exo_controls_next_description">"次のトラック"</string>
<string name="pause_description">"一時停止"</string> <string name="exo_controls_pause_description">"一時停止"</string>
<string name="play_description">"再生"</string> <string name="exo_controls_play_description">"再生"</string>
<string name="stop_description">"停止"</string> <string name="exo_controls_stop_description">"停止"</string>
<string name="rew_description">"巻き戻し"</string> <string name="exo_controls_rewind_description">"巻き戻し"</string>
<string name="ffw_description">"早送り"</string> <string name="exo_controls_fastforward_description">"早送り"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"წინა ჩანაწერი"</string> <string name="exo_controls_previous_description">"წინა ჩანაწერი"</string>
<string name="next_description">"შემდეგი ჩანაწერი"</string> <string name="exo_controls_next_description">"შემდეგი ჩანაწერი"</string>
<string name="pause_description">"პაუზა"</string> <string name="exo_controls_pause_description">"პაუზა"</string>
<string name="play_description">"დაკვრა"</string> <string name="exo_controls_play_description">"დაკვრა"</string>
<string name="stop_description">"შეწყვეტა"</string> <string name="exo_controls_stop_description">"შეწყვეტა"</string>
<string name="rew_description">"უკან გადახვევა"</string> <string name="exo_controls_rewind_description">"უკან გადახვევა"</string>
<string name="ffw_description">"წინ გადახვევა"</string> <string name="exo_controls_fastforward_description">"წინ გადახვევა"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Алдыңғы трек"</string> <string name="exo_controls_previous_description">"Алдыңғы трек"</string>
<string name="next_description">"Келесі трек"</string> <string name="exo_controls_next_description">"Келесі трек"</string>
<string name="pause_description">"Кідірту"</string> <string name="exo_controls_pause_description">"Кідірту"</string>
<string name="play_description">"Ойнату"</string> <string name="exo_controls_play_description">"Ойнату"</string>
<string name="stop_description">"Тоқтату"</string> <string name="exo_controls_stop_description">"Тоқтату"</string>
<string name="rew_description">"Кері айналдыру"</string> <string name="exo_controls_rewind_description">"Кері айналдыру"</string>
<string name="ffw_description">"Жылдам алға айналдыру"</string> <string name="exo_controls_fastforward_description">"Жылдам алға айналдыру"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"បទ​មុន"</string> <string name="exo_controls_previous_description">"បទ​មុន"</string>
<string name="next_description">"បទ​បន្ទាប់"</string> <string name="exo_controls_next_description">"បទ​បន្ទាប់"</string>
<string name="pause_description">"ផ្អាក"</string> <string name="exo_controls_pause_description">"ផ្អាក"</string>
<string name="play_description">"ចាក់"</string> <string name="exo_controls_play_description">"ចាក់"</string>
<string name="stop_description">"បញ្ឈប់"</string> <string name="exo_controls_stop_description">"បញ្ឈប់"</string>
<string name="rew_description">"ខា​ថយក្រោយ"</string> <string name="exo_controls_rewind_description">"ខា​ថយក្រោយ"</string>
<string name="ffw_description">"ទៅ​មុខ​​​រហ័ស"</string> <string name="exo_controls_fastforward_description">"ទៅ​មុខ​​​រហ័ស"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್"</string> <string name="exo_controls_previous_description">"ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್"</string>
<string name="next_description">"ಮುಂದಿನ ಟ್ರ್ಯಾಕ್"</string> <string name="exo_controls_next_description">"ಮುಂದಿನ ಟ್ರ್ಯಾಕ್"</string>
<string name="pause_description">"ವಿರಾಮಗೊಳಿಸು"</string> <string name="exo_controls_pause_description">"ವಿರಾಮಗೊಳಿಸು"</string>
<string name="play_description">"ಪ್ಲೇ ಮಾಡು"</string> <string name="exo_controls_play_description">"ಪ್ಲೇ ಮಾಡು"</string>
<string name="stop_description">"ನಿಲ್ಲಿಸು"</string> <string name="exo_controls_stop_description">"ನಿಲ್ಲಿಸು"</string>
<string name="rew_description">"ರಿವೈಂಡ್ ಮಾಡು"</string> <string name="exo_controls_rewind_description">"ರಿವೈಂಡ್ ಮಾಡು"</string>
<string name="ffw_description">"ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ"</string> <string name="exo_controls_fastforward_description">"ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"이전 트랙"</string> <string name="exo_controls_previous_description">"이전 트랙"</string>
<string name="next_description">"다음 트랙"</string> <string name="exo_controls_next_description">"다음 트랙"</string>
<string name="pause_description">"일시중지"</string> <string name="exo_controls_pause_description">"일시중지"</string>
<string name="play_description">"재생"</string> <string name="exo_controls_play_description">"재생"</string>
<string name="stop_description">"중지"</string> <string name="exo_controls_stop_description">"중지"</string>
<string name="rew_description">"되감기"</string> <string name="exo_controls_rewind_description">"되감기"</string>
<string name="ffw_description">"빨리 감기"</string> <string name="exo_controls_fastforward_description">"빨리 감기"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Мурунку трек"</string> <string name="exo_controls_previous_description">"Мурунку трек"</string>
<string name="next_description">"Кийинки трек"</string> <string name="exo_controls_next_description">"Кийинки трек"</string>
<string name="pause_description">"Тындыруу"</string> <string name="exo_controls_pause_description">"Тындыруу"</string>
<string name="play_description">"Ойнотуу"</string> <string name="exo_controls_play_description">"Ойнотуу"</string>
<string name="stop_description">"Токтотуу"</string> <string name="exo_controls_stop_description">"Токтотуу"</string>
<string name="rew_description">"Артка түрүү"</string> <string name="exo_controls_rewind_description">"Артка түрүү"</string>
<string name="ffw_description">"Алдыга түрүү"</string> <string name="exo_controls_fastforward_description">"Алдыга түрүү"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"​ເພງ​ກ່ອນ​ໜ້າ"</string> <string name="exo_controls_previous_description">"​ເພງ​ກ່ອນ​ໜ້າ"</string>
<string name="next_description">"​ເພງ​ຕໍ່​ໄປ"</string> <string name="exo_controls_next_description">"​ເພງ​ຕໍ່​ໄປ"</string>
<string name="pause_description">"ຢຸດຊົ່ວຄາວ"</string> <string name="exo_controls_pause_description">"ຢຸດຊົ່ວຄາວ"</string>
<string name="play_description">"ຫຼິ້ນ"</string> <string name="exo_controls_play_description">"ຫຼິ້ນ"</string>
<string name="stop_description">"ຢຸດ"</string> <string name="exo_controls_stop_description">"ຢຸດ"</string>
<string name="rew_description">"​ຣີ​​ວາຍກັບ"</string> <string name="exo_controls_rewind_description">"​ຣີ​​ວາຍກັບ"</string>
<string name="ffw_description">"ເລື່ອນ​ໄປ​ໜ້າ"</string> <string name="exo_controls_fastforward_description">"ເລື່ອນ​ໄປ​ໜ້າ"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Ankstesnis takelis"</string> <string name="exo_controls_previous_description">"Ankstesnis takelis"</string>
<string name="next_description">"Kitas takelis"</string> <string name="exo_controls_next_description">"Kitas takelis"</string>
<string name="pause_description">"Pristabdyti"</string> <string name="exo_controls_pause_description">"Pristabdyti"</string>
<string name="play_description">"Leisti"</string> <string name="exo_controls_play_description">"Leisti"</string>
<string name="stop_description">"Stabdyti"</string> <string name="exo_controls_stop_description">"Stabdyti"</string>
<string name="rew_description">"Sukti atgal"</string> <string name="exo_controls_rewind_description">"Sukti atgal"</string>
<string name="ffw_description">"Sukti pirmyn"</string> <string name="exo_controls_fastforward_description">"Sukti pirmyn"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Iepriekšējais ieraksts"</string> <string name="exo_controls_previous_description">"Iepriekšējais ieraksts"</string>
<string name="next_description">"Nākamais ieraksts"</string> <string name="exo_controls_next_description">"Nākamais ieraksts"</string>
<string name="pause_description">"Pārtraukt"</string> <string name="exo_controls_pause_description">"Pārtraukt"</string>
<string name="play_description">"Atskaņot"</string> <string name="exo_controls_play_description">"Atskaņot"</string>
<string name="stop_description">"Apturēt"</string> <string name="exo_controls_stop_description">"Apturēt"</string>
<string name="rew_description">"Attīt atpakaļ"</string> <string name="exo_controls_rewind_description">"Attīt atpakaļ"</string>
<string name="ffw_description">"Ātri patīt"</string> <string name="exo_controls_fastforward_description">"Ātri patīt"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Претходна песна"</string> <string name="exo_controls_previous_description">"Претходна песна"</string>
<string name="next_description">"Следна песна"</string> <string name="exo_controls_next_description">"Следна песна"</string>
<string name="pause_description">"Пауза"</string> <string name="exo_controls_pause_description">"Пауза"</string>
<string name="play_description">"Пушти"</string> <string name="exo_controls_play_description">"Пушти"</string>
<string name="stop_description">"Запри"</string> <string name="exo_controls_stop_description">"Запри"</string>
<string name="rew_description">"Премотај назад"</string> <string name="exo_controls_rewind_description">"Премотај назад"</string>
<string name="ffw_description">"Брзо премотај напред"</string> <string name="exo_controls_fastforward_description">"Брзо премотај напред"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"മുമ്പത്തെ ട്രാക്ക്"</string> <string name="exo_controls_previous_description">"മുമ്പത്തെ ട്രാക്ക്"</string>
<string name="next_description">"അടുത്ത ട്രാക്ക്"</string> <string name="exo_controls_next_description">"അടുത്ത ട്രാക്ക്"</string>
<string name="pause_description">"താൽക്കാലികമായി നിർത്തുക"</string> <string name="exo_controls_pause_description">"താൽക്കാലികമായി നിർത്തുക"</string>
<string name="play_description">"പ്ലേ ചെയ്യുക"</string> <string name="exo_controls_play_description">"പ്ലേ ചെയ്യുക"</string>
<string name="stop_description">"നിര്‍ത്തുക"</string> <string name="exo_controls_stop_description">"നിര്‍ത്തുക"</string>
<string name="rew_description">"റിവൈൻഡുചെയ്യുക"</string> <string name="exo_controls_rewind_description">"റിവൈൻഡുചെയ്യുക"</string>
<string name="ffw_description">"വേഗത്തിലുള്ള കൈമാറൽ"</string> <string name="exo_controls_fastforward_description">"വേഗത്തിലുള്ള കൈമാറൽ"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Өмнөх трек"</string> <string name="exo_controls_previous_description">"Өмнөх трек"</string>
<string name="next_description">"Дараагийн трек"</string> <string name="exo_controls_next_description">"Дараагийн трек"</string>
<string name="pause_description">"Түр зогсоох"</string> <string name="exo_controls_pause_description">"Түр зогсоох"</string>
<string name="play_description">"Тоглуулах"</string> <string name="exo_controls_play_description">"Тоглуулах"</string>
<string name="stop_description">"Зогсоох"</string> <string name="exo_controls_stop_description">"Зогсоох"</string>
<string name="rew_description">"Буцааж хураах"</string> <string name="exo_controls_rewind_description">"Буцааж хураах"</string>
<string name="ffw_description">"Хурдан урагшлуулах"</string> <string name="exo_controls_fastforward_description">"Хурдан урагшлуулах"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"मागील ट्रॅक"</string> <string name="exo_controls_previous_description">"मागील ट्रॅक"</string>
<string name="next_description">"पुढील ट्रॅक"</string> <string name="exo_controls_next_description">"पुढील ट्रॅक"</string>
<string name="pause_description">"विराम द्या"</string> <string name="exo_controls_pause_description">"विराम द्या"</string>
<string name="play_description">"प्ले करा"</string> <string name="exo_controls_play_description">"प्ले करा"</string>
<string name="stop_description">"थांबा"</string> <string name="exo_controls_stop_description">"थांबा"</string>
<string name="rew_description">"रिवाईँड करा"</string> <string name="exo_controls_rewind_description">"रिवाईँड करा"</string>
<string name="ffw_description">"फास्ट फॉरवर्ड करा"</string> <string name="exo_controls_fastforward_description">"फास्ट फॉरवर्ड करा"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Lagu sebelumnya"</string> <string name="exo_controls_previous_description">"Lagu sebelumnya"</string>
<string name="next_description">"Lagu seterusnya"</string> <string name="exo_controls_next_description">"Lagu seterusnya"</string>
<string name="pause_description">"Jeda"</string> <string name="exo_controls_pause_description">"Jeda"</string>
<string name="play_description">"Main"</string> <string name="exo_controls_play_description">"Main"</string>
<string name="stop_description">"Berhenti"</string> <string name="exo_controls_stop_description">"Berhenti"</string>
<string name="rew_description">"Gulung semula"</string> <string name="exo_controls_rewind_description">"Gulung semula"</string>
<string name="ffw_description">"Mara laju"</string> <string name="exo_controls_fastforward_description">"Mara laju"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"ယခင် တစ်ပုဒ်"</string> <string name="exo_controls_previous_description">"ယခင် တစ်ပုဒ်"</string>
<string name="next_description">"နောက် တစ်ပုဒ်"</string> <string name="exo_controls_next_description">"နောက် တစ်ပုဒ်"</string>
<string name="pause_description">"ခဏရပ်ရန်"</string> <string name="exo_controls_pause_description">"ခဏရပ်ရန်"</string>
<string name="play_description">"ဖွင့်ရန်"</string> <string name="exo_controls_play_description">"ဖွင့်ရန်"</string>
<string name="stop_description">"ရပ်ရန်"</string> <string name="exo_controls_stop_description">"ရပ်ရန်"</string>
<string name="rew_description">"ပြန်ရစ်ရန်"</string> <string name="exo_controls_rewind_description">"ပြန်ရစ်ရန်"</string>
<string name="ffw_description">"ရှေ့သို့ သွားရန်"</string> <string name="exo_controls_fastforward_description">"ရှေ့သို့ သွားရန်"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Forrige spor"</string> <string name="exo_controls_previous_description">"Forrige spor"</string>
<string name="next_description">"Neste spor"</string> <string name="exo_controls_next_description">"Neste spor"</string>
<string name="pause_description">"Sett på pause"</string> <string name="exo_controls_pause_description">"Sett på pause"</string>
<string name="play_description">"Spill av"</string> <string name="exo_controls_play_description">"Spill av"</string>
<string name="stop_description">"Stopp"</string> <string name="exo_controls_stop_description">"Stopp"</string>
<string name="rew_description">"Tilbakespoling"</string> <string name="exo_controls_rewind_description">"Tilbakespoling"</string>
<string name="ffw_description">"Fremoverspoling"</string> <string name="exo_controls_fastforward_description">"Fremoverspoling"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"अघिल्लो ट्रयाक"</string> <string name="exo_controls_previous_description">"अघिल्लो ट्रयाक"</string>
<string name="next_description">"अर्को ट्रयाक"</string> <string name="exo_controls_next_description">"अर्को ट्रयाक"</string>
<string name="pause_description">"रोक्नुहोस्"</string> <string name="exo_controls_pause_description">"रोक्नुहोस्"</string>
<string name="play_description">"चलाउनुहोस्"</string> <string name="exo_controls_play_description">"चलाउनुहोस्"</string>
<string name="stop_description">"रोक्नुहोस्"</string> <string name="exo_controls_stop_description">"रोक्नुहोस्"</string>
<string name="rew_description">"दोहोर्याउनुहोस्"</string> <string name="exo_controls_rewind_description">"दोहोर्याउनुहोस्"</string>
<string name="ffw_description">"फास्ट फर्वार्ड"</string> <string name="exo_controls_fastforward_description">"फास्ट फर्वार्ड"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Vorig nummer"</string> <string name="exo_controls_previous_description">"Vorig nummer"</string>
<string name="next_description">"Volgend nummer"</string> <string name="exo_controls_next_description">"Volgend nummer"</string>
<string name="pause_description">"Onderbreken"</string> <string name="exo_controls_pause_description">"Onderbreken"</string>
<string name="play_description">"Afspelen"</string> <string name="exo_controls_play_description">"Afspelen"</string>
<string name="stop_description">"Stoppen"</string> <string name="exo_controls_stop_description">"Stoppen"</string>
<string name="rew_description">"Terugspoelen"</string> <string name="exo_controls_rewind_description">"Terugspoelen"</string>
<string name="ffw_description">"Vooruitspoelen"</string> <string name="exo_controls_fastforward_description">"Vooruitspoelen"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"ਪਿਛਲਾ ਟਰੈਕ"</string> <string name="exo_controls_previous_description">"ਪਿਛਲਾ ਟਰੈਕ"</string>
<string name="next_description">"ਅਗਲਾ ਟਰੈਕ"</string> <string name="exo_controls_next_description">"ਅਗਲਾ ਟਰੈਕ"</string>
<string name="pause_description">"ਰੋਕੋ"</string> <string name="exo_controls_pause_description">"ਰੋਕੋ"</string>
<string name="play_description">"ਪਲੇ ਕਰੋ"</string> <string name="exo_controls_play_description">"ਪਲੇ ਕਰੋ"</string>
<string name="stop_description">"ਰੋਕੋ"</string> <string name="exo_controls_stop_description">"ਰੋਕੋ"</string>
<string name="rew_description">"ਰੀਵਾਈਂਡ ਕਰੋ"</string> <string name="exo_controls_rewind_description">"ਰੀਵਾਈਂਡ ਕਰੋ"</string>
<string name="ffw_description">"ਅੱਗੇ ਭੇਜੋ"</string> <string name="exo_controls_fastforward_description">"ਅੱਗੇ ਭੇਜੋ"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Poprzedni utwór"</string> <string name="exo_controls_previous_description">"Poprzedni utwór"</string>
<string name="next_description">"Następny utwór"</string> <string name="exo_controls_next_description">"Następny utwór"</string>
<string name="pause_description">"Wstrzymaj"</string> <string name="exo_controls_pause_description">"Wstrzymaj"</string>
<string name="play_description">"Odtwórz"</string> <string name="exo_controls_play_description">"Odtwórz"</string>
<string name="stop_description">"Zatrzymaj"</string> <string name="exo_controls_stop_description">"Zatrzymaj"</string>
<string name="rew_description">"Przewiń do tyłu"</string> <string name="exo_controls_rewind_description">"Przewiń do tyłu"</string>
<string name="ffw_description">"Przewiń do przodu"</string> <string name="exo_controls_fastforward_description">"Przewiń do przodu"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Faixa anterior"</string> <string name="exo_controls_previous_description">"Faixa anterior"</string>
<string name="next_description">"Próxima faixa"</string> <string name="exo_controls_next_description">"Próxima faixa"</string>
<string name="pause_description">"Pausar"</string> <string name="exo_controls_pause_description">"Pausar"</string>
<string name="play_description">"Reproduzir"</string> <string name="exo_controls_play_description">"Reproduzir"</string>
<string name="stop_description">"Parar"</string> <string name="exo_controls_stop_description">"Parar"</string>
<string name="rew_description">"Retroceder"</string> <string name="exo_controls_rewind_description">"Retroceder"</string>
<string name="ffw_description">"Avançar"</string> <string name="exo_controls_fastforward_description">"Avançar"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Faixa anterior"</string> <string name="exo_controls_previous_description">"Faixa anterior"</string>
<string name="next_description">"Faixa seguinte"</string> <string name="exo_controls_next_description">"Faixa seguinte"</string>
<string name="pause_description">"Interromper"</string> <string name="exo_controls_pause_description">"Interromper"</string>
<string name="play_description">"Reproduzir"</string> <string name="exo_controls_play_description">"Reproduzir"</string>
<string name="stop_description">"Parar"</string> <string name="exo_controls_stop_description">"Parar"</string>
<string name="rew_description">"Rebobinar"</string> <string name="exo_controls_rewind_description">"Rebobinar"</string>
<string name="ffw_description">"Avançar"</string> <string name="exo_controls_fastforward_description">"Avançar"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Faixa anterior"</string> <string name="exo_controls_previous_description">"Faixa anterior"</string>
<string name="next_description">"Próxima faixa"</string> <string name="exo_controls_next_description">"Próxima faixa"</string>
<string name="pause_description">"Pausar"</string> <string name="exo_controls_pause_description">"Pausar"</string>
<string name="play_description">"Reproduzir"</string> <string name="exo_controls_play_description">"Reproduzir"</string>
<string name="stop_description">"Parar"</string> <string name="exo_controls_stop_description">"Parar"</string>
<string name="rew_description">"Retroceder"</string> <string name="exo_controls_rewind_description">"Retroceder"</string>
<string name="ffw_description">"Avançar"</string> <string name="exo_controls_fastforward_description">"Avançar"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Melodia anterioară"</string> <string name="exo_controls_previous_description">"Melodia anterioară"</string>
<string name="next_description">"Melodia următoare"</string> <string name="exo_controls_next_description">"Melodia următoare"</string>
<string name="pause_description">"Pauză"</string> <string name="exo_controls_pause_description">"Pauză"</string>
<string name="play_description">"Redați"</string> <string name="exo_controls_play_description">"Redați"</string>
<string name="stop_description">"Opriți"</string> <string name="exo_controls_stop_description">"Opriți"</string>
<string name="rew_description">"Derulați"</string> <string name="exo_controls_rewind_description">"Derulați"</string>
<string name="ffw_description">"Derulați rapid înainte"</string> <string name="exo_controls_fastforward_description">"Derulați rapid înainte"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Предыдущий трек"</string> <string name="exo_controls_previous_description">"Предыдущий трек"</string>
<string name="next_description">"Следующий трек"</string> <string name="exo_controls_next_description">"Следующий трек"</string>
<string name="pause_description">"Приостановить"</string> <string name="exo_controls_pause_description">"Приостановить"</string>
<string name="play_description">"Воспроизвести"</string> <string name="exo_controls_play_description">"Воспроизвести"</string>
<string name="stop_description">"Остановить"</string> <string name="exo_controls_stop_description">"Остановить"</string>
<string name="rew_description">"Перемотать назад"</string> <string name="exo_controls_rewind_description">"Перемотать назад"</string>
<string name="ffw_description">"Перемотать вперед"</string> <string name="exo_controls_fastforward_description">"Перемотать вперед"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"පෙර ගීතය"</string> <string name="exo_controls_previous_description">"පෙර ගීතය"</string>
<string name="next_description">"ඊළඟ ගීතය"</string> <string name="exo_controls_next_description">"ඊළඟ ගීතය"</string>
<string name="pause_description">"විරාමය"</string> <string name="exo_controls_pause_description">"විරාමය"</string>
<string name="play_description">"ධාවනය කරන්න"</string> <string name="exo_controls_play_description">"ධාවනය කරන්න"</string>
<string name="stop_description">"නතර කරන්න"</string> <string name="exo_controls_stop_description">"නතර කරන්න"</string>
<string name="rew_description">"නැවත ඔතන්න"</string> <string name="exo_controls_rewind_description">"නැවත ඔතන්න"</string>
<string name="ffw_description">"වේගයෙන් ඉදිරියට යන"</string> <string name="exo_controls_fastforward_description">"වේගයෙන් ඉදිරියට යන"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Predchádzajúca stopa"</string> <string name="exo_controls_previous_description">"Predchádzajúca stopa"</string>
<string name="next_description">"Ďalšia stopa"</string> <string name="exo_controls_next_description">"Ďalšia stopa"</string>
<string name="pause_description">"Pozastaviť"</string> <string name="exo_controls_pause_description">"Pozastaviť"</string>
<string name="play_description">"Prehrať"</string> <string name="exo_controls_play_description">"Prehrať"</string>
<string name="stop_description">"Zastaviť"</string> <string name="exo_controls_stop_description">"Zastaviť"</string>
<string name="rew_description">"Pretočiť späť"</string> <string name="exo_controls_rewind_description">"Pretočiť späť"</string>
<string name="ffw_description">"Pretočiť dopredu"</string> <string name="exo_controls_fastforward_description">"Pretočiť dopredu"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Prejšnja skladba"</string> <string name="exo_controls_previous_description">"Prejšnja skladba"</string>
<string name="next_description">"Naslednja skladba"</string> <string name="exo_controls_next_description">"Naslednja skladba"</string>
<string name="pause_description">"Zaustavi"</string> <string name="exo_controls_pause_description">"Zaustavi"</string>
<string name="play_description">"Predvajaj"</string> <string name="exo_controls_play_description">"Predvajaj"</string>
<string name="stop_description">"Ustavi"</string> <string name="exo_controls_stop_description">"Ustavi"</string>
<string name="rew_description">"Previj nazaj"</string> <string name="exo_controls_rewind_description">"Previj nazaj"</string>
<string name="ffw_description">"Previj naprej"</string> <string name="exo_controls_fastforward_description">"Previj naprej"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Kënga e mëparshme"</string> <string name="exo_controls_previous_description">"Kënga e mëparshme"</string>
<string name="next_description">"Kënga tjetër"</string> <string name="exo_controls_next_description">"Kënga tjetër"</string>
<string name="pause_description">"Pauzë"</string> <string name="exo_controls_pause_description">"Pauzë"</string>
<string name="play_description">"Luaj"</string> <string name="exo_controls_play_description">"Luaj"</string>
<string name="stop_description">"Ndalo"</string> <string name="exo_controls_stop_description">"Ndalo"</string>
<string name="rew_description">"Kthehu pas"</string> <string name="exo_controls_rewind_description">"Kthehu pas"</string>
<string name="ffw_description">"Përparo me shpejtësi"</string> <string name="exo_controls_fastforward_description">"Përparo me shpejtësi"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Претходна песма"</string> <string name="exo_controls_previous_description">"Претходна песма"</string>
<string name="next_description">"Следећа песма"</string> <string name="exo_controls_next_description">"Следећа песма"</string>
<string name="pause_description">"Пауза"</string> <string name="exo_controls_pause_description">"Пауза"</string>
<string name="play_description">"Пусти"</string> <string name="exo_controls_play_description">"Пусти"</string>
<string name="stop_description">"Заустави"</string> <string name="exo_controls_stop_description">"Заустави"</string>
<string name="rew_description">"Премотај уназад"</string> <string name="exo_controls_rewind_description">"Премотај уназад"</string>
<string name="ffw_description">"Премотај унапред"</string> <string name="exo_controls_fastforward_description">"Премотај унапред"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Föregående spår"</string> <string name="exo_controls_previous_description">"Föregående spår"</string>
<string name="next_description">"Nästa spår"</string> <string name="exo_controls_next_description">"Nästa spår"</string>
<string name="pause_description">"Pausa"</string> <string name="exo_controls_pause_description">"Pausa"</string>
<string name="play_description">"Spela upp"</string> <string name="exo_controls_play_description">"Spela upp"</string>
<string name="stop_description">"Avbryt"</string> <string name="exo_controls_stop_description">"Avbryt"</string>
<string name="rew_description">"Spola tillbaka"</string> <string name="exo_controls_rewind_description">"Spola tillbaka"</string>
<string name="ffw_description">"Snabbspola framåt"</string> <string name="exo_controls_fastforward_description">"Snabbspola framåt"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Wimbo uliotangulia"</string> <string name="exo_controls_previous_description">"Wimbo uliotangulia"</string>
<string name="next_description">"Wimbo unaofuata"</string> <string name="exo_controls_next_description">"Wimbo unaofuata"</string>
<string name="pause_description">"Sitisha"</string> <string name="exo_controls_pause_description">"Sitisha"</string>
<string name="play_description">"Cheza"</string> <string name="exo_controls_play_description">"Cheza"</string>
<string name="stop_description">"Simamisha"</string> <string name="exo_controls_stop_description">"Simamisha"</string>
<string name="rew_description">"Rudisha nyuma"</string> <string name="exo_controls_rewind_description">"Rudisha nyuma"</string>
<string name="ffw_description">"Peleka mbele kwa kasi"</string> <string name="exo_controls_fastforward_description">"Peleka mbele kwa kasi"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"முந்தைய ட்ராக்"</string> <string name="exo_controls_previous_description">"முந்தைய ட்ராக்"</string>
<string name="next_description">"அடுத்த ட்ராக்"</string> <string name="exo_controls_next_description">"அடுத்த ட்ராக்"</string>
<string name="pause_description">"இடைநிறுத்து"</string> <string name="exo_controls_pause_description">"இடைநிறுத்து"</string>
<string name="play_description">"இயக்கு"</string> <string name="exo_controls_play_description">"இயக்கு"</string>
<string name="stop_description">"நிறுத்து"</string> <string name="exo_controls_stop_description">"நிறுத்து"</string>
<string name="rew_description">"மீண்டும் காட்டு"</string> <string name="exo_controls_rewind_description">"மீண்டும் காட்டு"</string>
<string name="ffw_description">"வேகமாக முன்செல்"</string> <string name="exo_controls_fastforward_description">"வேகமாக முன்செல்"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"మునుపటి ట్రాక్"</string> <string name="exo_controls_previous_description">"మునుపటి ట్రాక్"</string>
<string name="next_description">"తదుపరి ట్రాక్"</string> <string name="exo_controls_next_description">"తదుపరి ట్రాక్"</string>
<string name="pause_description">"పాజ్ చేయి"</string> <string name="exo_controls_pause_description">"పాజ్ చేయి"</string>
<string name="play_description">"ప్లే చేయి"</string> <string name="exo_controls_play_description">"ప్లే చేయి"</string>
<string name="stop_description">"ఆపివేయి"</string> <string name="exo_controls_stop_description">"ఆపివేయి"</string>
<string name="rew_description">"రివైండ్ చేయి"</string> <string name="exo_controls_rewind_description">"రివైండ్ చేయి"</string>
<string name="ffw_description">"వేగంగా ఫార్వార్డ్ చేయి"</string> <string name="exo_controls_fastforward_description">"వేగంగా ఫార్వార్డ్ చేయి"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"แทร็กก่อนหน้า"</string> <string name="exo_controls_previous_description">"แทร็กก่อนหน้า"</string>
<string name="next_description">"แทร็กถัดไป"</string> <string name="exo_controls_next_description">"แทร็กถัดไป"</string>
<string name="pause_description">"หยุดชั่วคราว"</string> <string name="exo_controls_pause_description">"หยุดชั่วคราว"</string>
<string name="play_description">"เล่น"</string> <string name="exo_controls_play_description">"เล่น"</string>
<string name="stop_description">"หยุด"</string> <string name="exo_controls_stop_description">"หยุด"</string>
<string name="rew_description">"กรอกลับ"</string> <string name="exo_controls_rewind_description">"กรอกลับ"</string>
<string name="ffw_description">"กรอไปข้างหน้า"</string> <string name="exo_controls_fastforward_description">"กรอไปข้างหน้า"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Nakaraang track"</string> <string name="exo_controls_previous_description">"Nakaraang track"</string>
<string name="next_description">"Susunod na track"</string> <string name="exo_controls_next_description">"Susunod na track"</string>
<string name="pause_description">"I-pause"</string> <string name="exo_controls_pause_description">"I-pause"</string>
<string name="play_description">"I-play"</string> <string name="exo_controls_play_description">"I-play"</string>
<string name="stop_description">"Ihinto"</string> <string name="exo_controls_stop_description">"Ihinto"</string>
<string name="rew_description">"I-rewind"</string> <string name="exo_controls_rewind_description">"I-rewind"</string>
<string name="ffw_description">"I-fast forward"</string> <string name="exo_controls_fastforward_description">"I-fast forward"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Önceki parça"</string> <string name="exo_controls_previous_description">"Önceki parça"</string>
<string name="next_description">"Sonraki parça"</string> <string name="exo_controls_next_description">"Sonraki parça"</string>
<string name="pause_description">"Duraklat"</string> <string name="exo_controls_pause_description">"Duraklat"</string>
<string name="play_description">"Çal"</string> <string name="exo_controls_play_description">"Çal"</string>
<string name="stop_description">"Durdur"</string> <string name="exo_controls_stop_description">"Durdur"</string>
<string name="rew_description">"Geri sar"</string> <string name="exo_controls_rewind_description">"Geri sar"</string>
<string name="ffw_description">"İleri sar"</string> <string name="exo_controls_fastforward_description">"İleri sar"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Попередня композиція"</string> <string name="exo_controls_previous_description">"Попередня композиція"</string>
<string name="next_description">"Наступна композиція"</string> <string name="exo_controls_next_description">"Наступна композиція"</string>
<string name="pause_description">"Пауза"</string> <string name="exo_controls_pause_description">"Пауза"</string>
<string name="play_description">"Відтворити"</string> <string name="exo_controls_play_description">"Відтворити"</string>
<string name="stop_description">"Зупинити"</string> <string name="exo_controls_stop_description">"Зупинити"</string>
<string name="rew_description">"Перемотати назад"</string> <string name="exo_controls_rewind_description">"Перемотати назад"</string>
<string name="ffw_description">"Перемотати вперед"</string> <string name="exo_controls_fastforward_description">"Перемотати вперед"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"پچھلا ٹریک"</string> <string name="exo_controls_previous_description">"پچھلا ٹریک"</string>
<string name="next_description">"اگلا ٹریک"</string> <string name="exo_controls_next_description">"اگلا ٹریک"</string>
<string name="pause_description">"موقوف کریں"</string> <string name="exo_controls_pause_description">"موقوف کریں"</string>
<string name="play_description">"چلائیں"</string> <string name="exo_controls_play_description">"چلائیں"</string>
<string name="stop_description">"روکیں"</string> <string name="exo_controls_stop_description">"روکیں"</string>
<string name="rew_description">"ریوائینڈ کریں"</string> <string name="exo_controls_rewind_description">"ریوائینڈ کریں"</string>
<string name="ffw_description">"تیزی سے فارورڈ کریں"</string> <string name="exo_controls_fastforward_description">"تیزی سے فارورڈ کریں"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Avvalgi musiqa"</string> <string name="exo_controls_previous_description">"Avvalgi musiqa"</string>
<string name="next_description">"Keyingi musiqa"</string> <string name="exo_controls_next_description">"Keyingi musiqa"</string>
<string name="pause_description">"To‘xtatib turish"</string> <string name="exo_controls_pause_description">"To‘xtatib turish"</string>
<string name="play_description">"Ijro qilish"</string> <string name="exo_controls_play_description">"Ijro qilish"</string>
<string name="stop_description">"To‘xtatish"</string> <string name="exo_controls_stop_description">"To‘xtatish"</string>
<string name="rew_description">"Orqaga o‘tkazish"</string> <string name="exo_controls_rewind_description">"Orqaga o‘tkazish"</string>
<string name="ffw_description">"Oldinga o‘tkazish"</string> <string name="exo_controls_fastforward_description">"Oldinga o‘tkazish"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Bản nhạc trước"</string> <string name="exo_controls_previous_description">"Bản nhạc trước"</string>
<string name="next_description">"Bản nhạc tiếp theo"</string> <string name="exo_controls_next_description">"Bản nhạc tiếp theo"</string>
<string name="pause_description">"Tạm dừng"</string> <string name="exo_controls_pause_description">"Tạm dừng"</string>
<string name="play_description">"Phát"</string> <string name="exo_controls_play_description">"Phát"</string>
<string name="stop_description">"Ngừng"</string> <string name="exo_controls_stop_description">"Ngừng"</string>
<string name="rew_description">"Tua lại"</string> <string name="exo_controls_rewind_description">"Tua lại"</string>
<string name="ffw_description">"Tua đi"</string> <string name="exo_controls_fastforward_description">"Tua đi"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"上一曲"</string> <string name="exo_controls_previous_description">"上一曲"</string>
<string name="next_description">"下一曲"</string> <string name="exo_controls_next_description">"下一曲"</string>
<string name="pause_description">"暂停"</string> <string name="exo_controls_pause_description">"暂停"</string>
<string name="play_description">"播放"</string> <string name="exo_controls_play_description">"播放"</string>
<string name="stop_description">"停止"</string> <string name="exo_controls_stop_description">"停止"</string>
<string name="rew_description">"快退"</string> <string name="exo_controls_rewind_description">"快退"</string>
<string name="ffw_description">"快进"</string> <string name="exo_controls_fastforward_description">"快进"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"上一首曲目"</string> <string name="exo_controls_previous_description">"上一首曲目"</string>
<string name="next_description">"下一首曲目"</string> <string name="exo_controls_next_description">"下一首曲目"</string>
<string name="pause_description">"暫停"</string> <string name="exo_controls_pause_description">"暫停"</string>
<string name="play_description">"播放"</string> <string name="exo_controls_play_description">"播放"</string>
<string name="stop_description">"停止"</string> <string name="exo_controls_stop_description">"停止"</string>
<string name="rew_description">"倒帶"</string> <string name="exo_controls_rewind_description">"倒帶"</string>
<string name="ffw_description">"向前快轉"</string> <string name="exo_controls_fastforward_description">"向前快轉"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"上一首曲目"</string> <string name="exo_controls_previous_description">"上一首曲目"</string>
<string name="next_description">"下一首曲目"</string> <string name="exo_controls_next_description">"下一首曲目"</string>
<string name="pause_description">"暫停"</string> <string name="exo_controls_pause_description">"暫停"</string>
<string name="play_description">"播放"</string> <string name="exo_controls_play_description">"播放"</string>
<string name="stop_description">"停止"</string> <string name="exo_controls_stop_description">"停止"</string>
<string name="rew_description">"倒轉"</string> <string name="exo_controls_rewind_description">"倒轉"</string>
<string name="ffw_description">"快轉"</string> <string name="exo_controls_fastforward_description">"快轉"</string>
</resources> </resources>
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">"Ithrekhi yangaphambilini"</string> <string name="exo_controls_previous_description">"Ithrekhi yangaphambilini"</string>
<string name="next_description">"Ithrekhi elandelayo"</string> <string name="exo_controls_next_description">"Ithrekhi elandelayo"</string>
<string name="pause_description">"Misa isikhashana"</string> <string name="exo_controls_pause_description">"Misa isikhashana"</string>
<string name="play_description">"Dlala"</string> <string name="exo_controls_play_description">"Dlala"</string>
<string name="stop_description">"Misa"</string> <string name="exo_controls_stop_description">"Misa"</string>
<string name="rew_description">"Buyisela emumva"</string> <string name="exo_controls_rewind_description">"Buyisela emumva"</string>
<string name="ffw_description">"Ukudlulisa ngokushesha"</string> <string name="exo_controls_fastforward_description">"Ukudlulisa ngokushesha"</string>
</resources> </resources>
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
--> -->
<resources> <resources>
<declare-styleable name="SimpleExoPlayerView"> <declare-styleable name="SimpleExoPlayerView">
<attr name="use_controller" format="boolean" /> <attr name="use_controller" format="boolean"/>
<attr name="use_texture_view" format="boolean" /> <attr name="use_texture_view" format="boolean"/>
</declare-styleable> </declare-styleable>
</resources> </resources>
...@@ -14,12 +14,11 @@ ...@@ -14,12 +14,11 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="prev_description">Previous track</string> <string name="exo_controls_previous_description">Previous track</string>
<string name="next_description">Next track</string> <string name="exo_controls_next_description">Next track</string>
<string name="pause_description">Pause</string> <string name="exo_controls_pause_description">Pause</string>
<string name="play_description">Play</string> <string name="exo_controls_play_description">Play</string>
<string name="stop_description">Stop</string> <string name="exo_controls_stop_description">Stop</string>
<string name="rew_description">Rewind</string> <string name="exo_controls_rewind_description">Rewind</string>
<string name="ffw_description">Fast forward</string> <string name="exo_controls_fastforward_description">Fast forward</string>
</resources> </resources>
...@@ -15,31 +15,30 @@ ...@@ -15,31 +15,30 @@
--> -->
<resources> <resources>
<style name="MediaButton"> <style name="ExoMediaButton">
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:layout_width">71dip</item> <item name="android:layout_width">71dip</item>
<item name="android:layout_height">52dip</item> <item name="android:layout_height">52dip</item>
</style> </style>
<style name="MediaButton.Previous"> <style name="ExoMediaButton.Previous">
<item name="android:src">@drawable/ic_media_previous</item> <item name="android:src">@drawable/exo_controls_previous</item>
<item name="android:contentDescription">@string/prev_description</item> <item name="android:contentDescription">@string/exo_controls_previous_description</item>
</style> </style>
<style name="MediaButton.Next"> <style name="ExoMediaButton.Next">
<item name="android:src">@drawable/ic_media_next</item> <item name="android:src">@drawable/exo_controls_next</item>
<item name="android:contentDescription">@string/next_description</item> <item name="android:contentDescription">@string/exo_controls_next_description</item>
</style> </style>
<style name="MediaButton.Ffwd"> <style name="ExoMediaButton.FastForward">
<item name="android:src">@drawable/ic_media_ff</item> <item name="android:src">@drawable/exo_controls_fastforward</item>
<item name="android:contentDescription">@string/ffw_description</item> <item name="android:contentDescription">@string/exo_controls_fastforward_description</item>
</style> </style>
<style name="MediaButton.Rew"> <style name="ExoMediaButton.Rewind">
<item name="android:src">@drawable/ic_media_rew</item> <item name="android:src">@drawable/exo_controls_rewind</item>
<item name="android:contentDescription">@string/rew_description</item> <item name="android:contentDescription">@string/exo_controls_rewind_description</item>
</style> </style>
</resources> </resources>
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.playbacktests" package="com.google.android.exoplayer2.playbacktests"
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.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
......
...@@ -84,7 +84,7 @@ public final class FakeExtractorInput implements ExtractorInput { ...@@ -84,7 +84,7 @@ public final class FakeExtractorInput implements ExtractorInput {
* @param position The position to set. * @param position The position to set.
*/ */
public void setPosition(int position) { public void setPosition(int position) {
Assert.assertTrue(0 <= position && position < data.length); Assert.assertTrue(0 <= position && position <= data.length);
readPosition = position; readPosition = position;
peekPosition = position; peekPosition = position;
} }
...@@ -203,7 +203,7 @@ public final class FakeExtractorInput implements ExtractorInput { ...@@ -203,7 +203,7 @@ public final class FakeExtractorInput implements ExtractorInput {
peekPosition = readPosition; peekPosition = readPosition;
throw new SimulatedIOException("Simulated IO error at position: " + position); throw new SimulatedIOException("Simulated IO error at position: " + position);
} }
if (isEof()) { if (length > 0 && position == data.length) {
if (allowEndOfInput) { if (allowEndOfInput) {
return false; return false;
} }
...@@ -217,6 +217,10 @@ public final class FakeExtractorInput implements ExtractorInput { ...@@ -217,6 +217,10 @@ public final class FakeExtractorInput implements ExtractorInput {
} }
private int getReadLength(int requestedLength) { private int getReadLength(int requestedLength) {
if (readPosition == data.length) {
// If the requested length is non-zero, the end of the input will be read.
return requestedLength == 0 ? 0 : Integer.MAX_VALUE;
}
int targetPosition = readPosition + requestedLength; int targetPosition = readPosition + requestedLength;
if (simulatePartialReads && requestedLength > 1 if (simulatePartialReads && requestedLength > 1
&& !partiallySatisfiedTargetPositions.get(targetPosition)) { && !partiallySatisfiedTargetPositions.get(targetPosition)) {
...@@ -226,10 +230,6 @@ public final class FakeExtractorInput implements ExtractorInput { ...@@ -226,10 +230,6 @@ public final class FakeExtractorInput implements ExtractorInput {
return Math.min(requestedLength, data.length - readPosition); return Math.min(requestedLength, data.length - readPosition);
} }
private boolean isEof() {
return readPosition == data.length;
}
/** /**
* Builder of {@link FakeExtractorInput} instances. * Builder of {@link FakeExtractorInput} instances.
*/ */
......
...@@ -88,7 +88,8 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { ...@@ -88,7 +88,8 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable {
} }
@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) {
sampleTimesUs.add(timeUs); sampleTimesUs.add(timeUs);
sampleFlags.add(flags); sampleFlags.add(flags);
sampleStartOffsets.add(sampleData.length - offset - size); sampleStartOffsets.add(sampleData.length - offset - size);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment