diff --git a/README.md b/README.md index 970d482e16..f9c422db5e 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,17 @@ and extend, and can be updated through Play Store application updates. #### Via jCenter #### -The easiest way to get started using ExoPlayer is by including the following in -your project's `build.gradle` file: +The easiest way to get started using ExoPlayer is to add it as a gradle +dependency. You need to make sure you have the jcenter repository included in +the `build.gradle` file in the root of your project: + +```gradle +repositories { + jcenter() +} +``` + +Next, include the following in your module's `build.gradle` file: ```gradle compile 'com.google.android.exoplayer:exoplayer:rX.X.X' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fb3b33d696..d53d153912 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,14 @@ # Release notes # +### r2.0.1 ### + +* Fix playback of short duration content + ([#1837](https://github.com/google/ExoPlayer/issues/1837)). +* Fix MergingMediaSource preparation issue + ([#1853](https://github.com/google/ExoPlayer/issues/1853)). +* Fix live stream buffering (out of memory) issue + ([#1825](https://github.com/google/ExoPlayer/issues/1825)). + ### r2.0.0 ### ExoPlayer 2.x is a major iteration of the library. It includes significant API diff --git a/demo/build.gradle b/demo/build.gradle index 6aaedf05c5..593796bda5 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -39,14 +39,14 @@ android { productFlavors { demo - demo_ext + demoExt } } dependencies { compile project(':library') - demo_extCompile project(path: ':extension-ffmpeg') - demo_extCompile project(path: ':extension-flac') - demo_extCompile project(path: ':extension-opus') - demo_extCompile project(path: ':extension-vp9') + demoExtCompile project(path: ':extension-ffmpeg') + demoExtCompile project(path: ':extension-flac') + demoExtCompile project(path: ':extension-opus') + demoExtCompile project(path: ':extension-vp9') } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 6cf98d2aca..b270b1410f 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2001" + android:versionName="2.0.1"> diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java index 9e927488a7..92dc08597f 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -16,9 +16,33 @@ package com.google.android.exoplayer2.demo; import android.app.Application; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.util.Util; /** * Placeholder application to facilitate overriding Application methods for debugging and testing. */ public class DemoApplication extends Application { + + protected String userAgent; + + @Override + public void onCreate() { + super.onCreate(); + userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); + } + + DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { + return new DefaultDataSourceFactory(this, bandwidthMeter, + buildHttpDataSourceFactory(bandwidthMeter)); + } + + HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { + return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter); + } + } diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 7fd32ddbe1..8c12290d35 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -38,9 +38,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.io.IOException; @@ -54,7 +55,7 @@ import java.util.Locale; /* package */ final class EventLogger implements ExoPlayer.EventListener, AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener, ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, - MappingTrackSelector.EventListener, MetadataRenderer.Output> { + TrackSelector.EventListener, MetadataRenderer.Output> { private static final String TAG = "EventLogger"; private static final int MAX_TIMELINE_ITEM_LINES = 3; @@ -125,23 +126,24 @@ import java.util.Locale; // MappingTrackSelector.EventListener @Override - public void onTracksChanged(TrackInfo trackInfo) { + public void onTrackSelectionsChanged(TrackSelections trackSelections) { Log.d(TAG, "Tracks ["); // Log tracks associated to renderers. - for (int rendererIndex = 0; rendererIndex < trackInfo.rendererCount; rendererIndex++) { - TrackGroupArray trackGroups = trackInfo.getTrackGroups(rendererIndex); - TrackSelection trackSelection = trackInfo.getTrackSelection(rendererIndex); + MappedTrackInfo info = trackSelections.info; + for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) { + TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex); + TrackSelection trackSelection = trackSelections.get(rendererIndex); if (trackGroups.length > 0) { Log.d(TAG, " Renderer:" + rendererIndex + " ["); for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { TrackGroup trackGroup = trackGroups.get(groupIndex); String adaptiveSupport = getAdaptiveSupportString( - trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)); + trackGroup.length, info.getAdaptiveSupport(rendererIndex, groupIndex, false)); Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " ["); for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); String formatSupport = getFormatSupportString( - trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); + info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); Log.d(TAG, " " + status + " Track:" + trackIndex + ", " + getFormatString(trackGroup.getFormat(trackIndex)) + ", supported=" + formatSupport); @@ -152,7 +154,7 @@ import java.util.Locale; } } // Log tracks not associated with a renderer. - TrackGroupArray trackGroups = trackInfo.getUnassociatedTrackGroups(); + TrackGroupArray trackGroups = info.getUnassociatedTrackGroups(); if (trackGroups.length > 0) { Log.d(TAG, " Renderer:None ["); for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index eaa4d9971f..667ccd5bab 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; @@ -55,15 +56,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.ui.DebugTextViewHelper; import com.google.android.exoplayer2.ui.PlaybackControlView; import com.google.android.exoplayer2.ui.SimpleExoPlayerView; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.Util; import java.net.CookieHandler; @@ -77,7 +78,7 @@ import java.util.UUID; * An activity that plays media using {@link SimpleExoPlayer}. */ public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener, - MappingTrackSelector.EventListener, PlaybackControlView.VisibilityListener { + TrackSelector.EventListener, PlaybackControlView.VisibilityListener { public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; public static final String DRM_LICENSE_URL = "drm_license_url"; @@ -106,7 +107,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay private TextView debugTextView; private Button retryButton; - private String userAgent; private DataSource.Factory mediaDataSourceFactory; private SimpleExoPlayer player; private MappingTrackSelector trackSelector; @@ -125,7 +125,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); shouldAutoPlay = true; - userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); mediaDataSourceFactory = buildDataSourceFactory(true); mainHandler = new Handler(); if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { @@ -203,7 +202,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay initializePlayer(); } else if (view.getParent() == debugRootView) { trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(), - trackSelector.getTrackInfo(), (int) view.getTag()); + trackSelector.getCurrentSelections().info, (int) view.getTag()); } } @@ -222,7 +221,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA) ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null; - DrmSessionManager drmSessionManager = null; + DrmSessionManager drmSessionManager = null; if (drmSchemeUuid != null) { String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL); String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES); @@ -316,15 +315,15 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension : uri.getLastPathSegment()); switch (type) { - case Util.TYPE_SS: + case C.TYPE_SS: return new SsMediaSource(uri, buildDataSourceFactory(false), new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); - case Util.TYPE_DASH: + case C.TYPE_DASH: return new DashMediaSource(uri, buildDataSourceFactory(false), new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger); - case Util.TYPE_HLS: + case C.TYPE_HLS: return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger); - case Util.TYPE_OTHER: + case C.TYPE_OTHER: return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, eventLogger); default: { @@ -333,9 +332,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } - private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, - Map keyRequestProperties) - throws UnsupportedDrmException { + private DrmSessionManager buildDrmSessionManager(UUID uuid, + String licenseUrl, Map keyRequestProperties) throws UnsupportedDrmException { if (Util.SDK_INT < 18) { return null; } @@ -376,8 +374,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay * @return A new DataSource factory. */ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { - return new DefaultDataSourceFactory(this, useBandwidthMeter ? BANDWIDTH_METER : null, - buildHttpDataSourceFactory(useBandwidthMeter)); + return ((DemoApplication) getApplication()) + .buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); } /** @@ -388,7 +386,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay * @return A new HttpDataSource factory. */ private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { - return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null); + return ((DemoApplication) getApplication()) + .buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); } // ExoPlayer.EventListener implementation @@ -452,8 +451,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay // MappingTrackSelector.EventListener implementation @Override - public void onTracksChanged(TrackInfo trackInfo) { + public void onTrackSelectionsChanged(TrackSelections trackSelections) { updateButtonVisibilities(); + MappedTrackInfo trackInfo = trackSelections.info; if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { showToast(R.string.error_unsupported_video); } @@ -474,14 +474,14 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay return; } - TrackInfo trackInfo = trackSelector.getTrackInfo(); - if (trackInfo == null) { + TrackSelections trackSelections = trackSelector.getCurrentSelections(); + if (trackSelections == null) { return; } - int rendererCount = trackInfo.rendererCount; + int rendererCount = trackSelections.length; for (int i = 0; i < rendererCount; i++) { - TrackGroupArray trackGroups = trackInfo.getTrackGroups(i); + TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i); if (trackGroups.length != 0) { Button button = new Button(this); int label; diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java index 1bbd620660..8892c138d0 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java @@ -31,8 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.util.MimeTypes; @@ -51,7 +51,7 @@ import java.util.Locale; private final MappingTrackSelector selector; private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; - private TrackInfo trackInfo; + private MappedTrackInfo trackInfo; private int rendererIndex; private TrackGroupArray trackGroups; private boolean[] trackGroupsAdaptive; @@ -82,7 +82,7 @@ import java.util.Locale; * @param trackInfo The current track information. * @param rendererIndex The index of the renderer. */ - public void showSelectionDialog(Activity activity, CharSequence title, TrackInfo trackInfo, + public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo, int rendererIndex) { this.trackInfo = trackInfo; this.rendererIndex = rendererIndex; @@ -203,11 +203,7 @@ import java.util.Locale; @Override public void onClick(DialogInterface dialog, int which) { - if (isDisabled) { - selector.setRendererDisabled(rendererIndex, true); - return; - } - selector.setRendererDisabled(rendererIndex, false); + selector.setRendererDisabled(rendererIndex, isDisabled); if (override != null) { selector.setSelectionOverride(rendererIndex, trackGroups, override); } else { diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index fca73af98b..ccd4dec191 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -100,7 +100,8 @@ public final class CronetDataSourceTest { Executor executor, int priority, Collection connectionAnnotations, boolean disableCache, - boolean disableConnectionMigration); + boolean disableConnectionMigration, + boolean allowDirectExecutor); } @Mock @@ -108,7 +109,7 @@ public final class CronetDataSourceTest { @Mock private Predicate mockContentTypePredicate; @Mock - private TransferListener mockTransferListener; + private TransferListener mockTransferListener; @Mock private Clock mockClock; @Mock @@ -143,6 +144,7 @@ public final class CronetDataSourceTest { anyInt(), eq(Collections.emptyList()), any(Boolean.class), + any(Boolean.class), any(Boolean.class))).thenReturn(mockUrlRequest); mockStatusResponse(); @@ -170,8 +172,8 @@ public final class CronetDataSourceTest { } @Test(expected = IllegalStateException.class) - public void testOpeningTwiceThrows() throws HttpDataSourceException, IllegalStateException { - mockResponesStartSuccess(); + public void testOpeningTwiceThrows() throws HttpDataSourceException { + mockResponseStartSuccess(); assertConnectionState(CronetDataSource.IDLE_CONNECTION); dataSourceUnderTest.open(testDataSpec); @@ -181,7 +183,7 @@ public final class CronetDataSourceTest { @Test public void testCallbackFromPreviousRequest() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.close(); @@ -194,6 +196,7 @@ public final class CronetDataSourceTest { anyInt(), eq(Collections.emptyList()), any(Boolean.class), + any(Boolean.class), any(Boolean.class))).thenReturn(mockUrlRequest2); doAnswer(new Answer() { @Override @@ -214,7 +217,7 @@ public final class CronetDataSourceTest { @Test public void testRequestStartCalled() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockCronetEngine).createRequest( @@ -224,13 +227,14 @@ public final class CronetDataSourceTest { anyInt(), eq(Collections.emptyList()), any(Boolean.class), + any(Boolean.class), any(Boolean.class)); verify(mockUrlRequest).start(); } @Test public void testRequestHeadersSet() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testResponseHeader.put("Content-Length", Long.toString(5000L)); @@ -248,13 +252,29 @@ public final class CronetDataSourceTest { @Test public void testRequestOpen() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec)); assertConnectionState(CronetDataSource.OPEN_CONNECTION); verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); } + + @Test + public void testRequestOpenGzippedCompressedReturnsDataSpecLength() + throws HttpDataSourceException { + testResponseHeader.put("Content-Encoding", "gzip"); + testUrlResponseInfo = createUrlResponseInfo(200); // statusCode + mockResponseStartSuccess(); + + // Data spec's requested length, 5000. Test response's length, 16,000. + testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); + + assertEquals(5000 /* contentLength */, dataSourceUnderTest.open(testDataSpec)); + assertConnectionState(CronetDataSource.OPEN_CONNECTION); + verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); + } + @Test public void testRequestOpenFail() { mockResponseStartFailure(); @@ -291,7 +311,7 @@ public final class CronetDataSourceTest { @Test public void testRequestOpenValidatesStatusCode() { - mockResponesStartSuccess(); + mockResponseStartSuccess(); testUrlResponseInfo = createUrlResponseInfo(500); // statusCode try { @@ -308,7 +328,7 @@ public final class CronetDataSourceTest { @Test public void testRequestOpenValidatesContentTypePredicate() { - mockResponesStartSuccess(); + mockResponseStartSuccess(); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(false); try { @@ -325,7 +345,7 @@ public final class CronetDataSourceTest { @Test public void testRequestOpenValidatesContentLength() { - mockResponesStartSuccess(); + mockResponseStartSuccess(); // Data spec's requested length, 5000. Test response's length, 16,000. testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); @@ -344,7 +364,7 @@ public final class CronetDataSourceTest { @Test public void testPostRequestOpen() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec)); @@ -354,7 +374,7 @@ public final class CronetDataSourceTest { @Test public void testPostRequestOpenValidatesContentType() { - mockResponesStartSuccess(); + mockResponseStartSuccess(); try { dataSourceUnderTest.open(testPostDataSpec); @@ -366,7 +386,7 @@ public final class CronetDataSourceTest { @Test public void testPostRequestOpenRejects307Redirects() { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockResponseStartRedirect(); try { @@ -380,7 +400,7 @@ public final class CronetDataSourceTest { @Test public void testRequestReadTwice() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadSuccess(); dataSourceUnderTest.open(testDataSpec); @@ -402,7 +422,7 @@ public final class CronetDataSourceTest { @Test public void testSecondRequestNoContentLength() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadSuccess(); byte[] returnedBuffer = new byte[8]; @@ -433,7 +453,23 @@ public final class CronetDataSourceTest { @Test public void testReadWithOffset() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); + mockReadSuccess(); + + dataSourceUnderTest.open(testDataSpec); + + byte[] returnedBuffer = new byte[16]; + int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8); + assertArrayEquals(prefixZeros(buildTestDataArray(0, 8), 16), returnedBuffer); + assertEquals(8, bytesRead); + verify(mockTransferListener).onBytesTransferred(dataSourceUnderTest, 8); + } + + @Test + public void testReadWithUnsetLength() throws HttpDataSourceException { + testResponseHeader.remove("Content-Length"); + testUrlResponseInfo = createUrlResponseInfo(200); // statusCode + mockResponseStartSuccess(); mockReadSuccess(); dataSourceUnderTest.open(testDataSpec); @@ -447,7 +483,7 @@ public final class CronetDataSourceTest { @Test public void testReadReturnsWhatItCan() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadSuccess(); dataSourceUnderTest.open(testDataSpec); @@ -461,7 +497,7 @@ public final class CronetDataSourceTest { @Test public void testClosedMeansClosed() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadSuccess(); int bytesRead = 0; @@ -489,7 +525,7 @@ public final class CronetDataSourceTest { @Test public void testOverread() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadSuccess(); // Ask for 16 bytes @@ -676,7 +712,7 @@ public final class CronetDataSourceTest { @Test public void testExceptionFromTransferListener() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); // Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that // the subsequent open() call succeeds. @@ -695,7 +731,7 @@ public final class CronetDataSourceTest { @Test public void testReadFailure() throws HttpDataSourceException { - mockResponesStartSuccess(); + mockResponseStartSuccess(); mockReadFailure(); dataSourceUnderTest.open(testDataSpec); @@ -722,7 +758,7 @@ public final class CronetDataSourceTest { }).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class)); } - private void mockResponesStartSuccess() { + private void mockResponseStartSuccess() { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 401941addc..fe8fd13abe 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -300,15 +300,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou try { validateResponse(info); responseInfo = info; - // Check content length. - contentLength = getContentLength(info.getAllHeaders()); - // If a specific length is requested and a specific length is returned but the 2 don't match - // it's an error. - if (currentDataSpec.length != C.LENGTH_UNSET - && contentLength != C.LENGTH_UNSET - && currentDataSpec.length != contentLength) { - throw new OpenException("Content length did not match requested length", currentDataSpec, - getCurrentRequestStatus()); + + if (isCompressed(info)) { + contentLength = currentDataSpec.length; + } else { + // Check content length. + contentLength = getContentLength(info.getAllHeaders()); + // If a specific length is requested and a specific length is returned but the 2 don't match + // it's an error. + if (currentDataSpec.length != C.LENGTH_UNSET + && contentLength != C.LENGTH_UNSET + && currentDataSpec.length != contentLength) { + throw new OpenException("Content length did not match requested length", currentDataSpec, + getCurrentRequestStatus()); + } } if (contentLength > 0) { @@ -326,6 +331,23 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou } } + /** + * Returns {@code true} iff the content is compressed. + * + *

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 entry : info.getAllHeadersAsList()) { + if (entry.getKey().equalsIgnoreCase("Content-Encoding")) { + return !entry.getValue().equalsIgnoreCase("identity"); + } + } + + return false; + } + private void validateResponse(UrlResponseInfo info) throws HttpDataSourceException { // Check for a valid response code. int responseCode = info.getHttpStatusCode(); diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 5df912ace2..29a22f380a 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac; import android.content.Context; import android.net.Uri; +import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -71,7 +72,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 1f554cbebc..4f723698a4 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.opus; import android.content.Context; import android.net.Uri; +import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -71,7 +72,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 553157605e..c5f61cf231 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.vp9; import android.content.Context; import android.net.Uri; +import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -87,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(null); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( diff --git a/library/build.gradle b/library/build.gradle index d301e0903e..c0fdfc1d14 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -55,6 +55,7 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile 'com.android.support:support-annotations:24.2.0' } android.libraryVariants.all { variant -> @@ -95,7 +96,7 @@ publish { userOrg = 'google' groupId = 'com.google.android.exoplayer' artifactId = 'exoplayer' - version = 'r2.0.0' + version = 'r2.0.1' description = 'The ExoPlayer library.' website = 'https://github.com/google/ExoPlayer' } diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3 b/library/src/androidTest/assets/mp3/play-trimmed.mp3 new file mode 100644 index 0000000000..d103ac5781 Binary files /dev/null and b/library/src/androidTest/assets/mp3/play-trimmed.mp3 differ diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump b/library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump new file mode 100644 index 0000000000..37a04215ee --- /dev/null +++ b/library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump @@ -0,0 +1,33 @@ +seekMap: + isSeekable = true + duration = 26125 + getPosition(0) = 0 +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/mpeg + maxInputSize = 4096 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 1 + sample 0: + time = 0 + flags = 1 + data = length 418, hash B819987 +tracksEnded = true diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump b/library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump new file mode 100644 index 0000000000..0b6516ccdb --- /dev/null +++ b/library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump @@ -0,0 +1,29 @@ +seekMap: + isSeekable = true + duration = 26125 + getPosition(0) = 0 +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/mpeg + maxInputSize = 4096 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 0 +tracksEnded = true diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump b/library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump new file mode 100644 index 0000000000..0b6516ccdb --- /dev/null +++ b/library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump @@ -0,0 +1,29 @@ +seekMap: + isSeekable = true + duration = 26125 + getPosition(0) = 0 +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/mpeg + maxInputSize = 4096 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 0 +tracksEnded = true diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump b/library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump new file mode 100644 index 0000000000..0b6516ccdb --- /dev/null +++ b/library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump @@ -0,0 +1,29 @@ +seekMap: + isSeekable = true + duration = 26125 + getPosition(0) = 0 +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/mpeg + maxInputSize = 4096 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 0 +tracksEnded = true diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump b/library/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump new file mode 100644 index 0000000000..b75aefd91b --- /dev/null +++ b/library/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump @@ -0,0 +1,33 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = 0 +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/mpeg + maxInputSize = 4096 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 44100 + pcmEncoding = -1 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 1 + sample 0: + time = 0 + flags = 1 + data = length 418, hash B819987 +tracksEnded = true diff --git a/library/src/androidTest/assets/rawcc/sample.rawcc.0.dump b/library/src/androidTest/assets/rawcc/sample.rawcc.0.dump index 13e7a93f19..3e84813162 100644 --- a/library/src/androidTest/assets/rawcc/sample.rawcc.0.dump +++ b/library/src/androidTest/assets/rawcc/sample.rawcc.0.dump @@ -8,7 +8,7 @@ track 0: bitrate = -1 id = null containerMimeType = null - sampleMimeType = application/eia-608 + sampleMimeType = application/cea-608 maxInputSize = -1 width = -1 height = -1 @@ -25,305 +25,605 @@ track 0: language = null drmInitData = - initializationData: - sample count = 75 + sample count = 150 sample 0: time = 37657512133 flags = 1 - data = length 2, hash FFFFF3C1 + data = length 3, hash 7363 sample 1: + time = 37657528822 + flags = 1 + data = length 3, hash 7724 + sample 2: time = 37657545511 flags = 1 - data = length 2, hash FFFFF6CD - sample 2: + data = length 3, hash 766F + sample 3: + time = 37657562177 + flags = 1 + data = length 3, hash 7724 + sample 4: time = 37657578866 flags = 1 - data = length 2, hash FFFFF6DC - sample 3: + data = length 3, hash 767E + sample 5: + time = 37657595555 + flags = 1 + data = length 3, hash 7724 + sample 6: time = 37657612244 flags = 1 - data = length 2, hash FFFFF65B - sample 4: + data = length 15, hash E4359178 + sample 7: + time = 37657628911 + flags = 1 + data = length 3, hash 7724 + sample 8: time = 37657645600 flags = 1 - data = length 2, hash FFFFF6CD - sample 5: + data = length 12, hash 15EBEB66 + sample 9: + time = 37657662288 + flags = 1 + data = length 3, hash 7724 + sample 10: time = 37657678977 flags = 1 - data = length 2, hash FFFFF67B - sample 6: + data = length 3, hash 761D + sample 11: + time = 37657695644 + flags = 1 + data = length 3, hash 7724 + sample 12: time = 37657712333 flags = 1 - data = length 2, hash 2B5 - sample 7: + data = length 30, hash E181418F + sample 13: + time = 37657729022 + flags = 1 + data = length 6, hash 36289CE2 + sample 14: time = 37657745711 flags = 1 - data = length 2, hash F5 - sample 8: + data = length 12, hash 3C304F5B + sample 15: + time = 37657762377 + flags = 1 + data = length 3, hash 7724 + sample 16: time = 37657779066 flags = 1 - data = length 2, hash FFFFF87A - sample 9: + data = length 12, hash 88DD8EF6 + sample 17: + time = 37657795755 + flags = 1 + data = length 3, hash 7724 + sample 18: time = 37657812444 flags = 1 - data = length 2, hash FFFFF698 - sample 10: + data = length 12, hash 8B411833 + sample 19: + time = 37657829111 + flags = 1 + data = length 3, hash 7724 + sample 20: time = 37657845800 flags = 1 - data = length 2, hash 1F4 - sample 11: + data = length 12, hash 742A2DF1 + sample 21: + time = 37657862488 + flags = 1 + data = length 3, hash 7724 + sample 22: time = 37657879177 flags = 1 - data = length 2, hash 803 - sample 12: + data = length 12, hash 9A2ECBEE + sample 23: + time = 37657895844 + flags = 1 + data = length 3, hash 7724 + sample 24: time = 37657912533 flags = 1 - data = length 2, hash 1F8 - sample 13: + data = length 12, hash 562688EA + sample 25: + time = 37657929222 + flags = 1 + data = length 3, hash 7724 + sample 26: time = 37657945911 flags = 1 - data = length 2, hash 117A - sample 14: + data = length 12, hash ADE4B953 + sample 27: + time = 37657962577 + flags = 1 + data = length 3, hash 7724 + sample 28: time = 37657979266 flags = 1 - data = length 2, hash 166 - sample 15: + data = length 12, hash F927E3E5 + sample 29: + time = 37657995955 + flags = 1 + data = length 3, hash 7724 + sample 30: time = 37658012644 flags = 1 - data = length 2, hash 105A - sample 16: + data = length 12, hash EA327945 + sample 31: + time = 37658029311 + flags = 1 + data = length 3, hash 7724 + sample 32: time = 37658046000 flags = 1 - data = length 2, hash FCF - sample 17: + data = length 12, hash 3E5DA13C + sample 33: + time = 37658062688 + flags = 1 + data = length 3, hash 7724 + sample 34: time = 37658079377 flags = 1 - data = length 2, hash 1253 - sample 18: + data = length 12, hash BF646AE3 + sample 35: + time = 37658096044 + flags = 1 + data = length 3, hash 7724 + sample 36: time = 37658112733 flags = 1 - data = length 2, hash 11DA - sample 19: + data = length 12, hash 41E3BA78 + sample 37: + time = 37658129422 + flags = 1 + data = length 3, hash 7724 + sample 38: time = 37658146111 flags = 1 - data = length 2, hash 795 - sample 20: + data = length 12, hash A2945EF6 + sample 39: + time = 37658162777 + flags = 1 + data = length 3, hash 7724 + sample 40: time = 37658179466 flags = 1 - data = length 2, hash 103E - sample 21: + data = length 12, hash 26735812 + sample 41: + time = 37658196155 + flags = 1 + data = length 3, hash 7724 + sample 42: time = 37658212844 flags = 1 - data = length 2, hash 120F - sample 22: + data = length 12, hash DC14D3D8 + sample 43: + time = 37658229511 + flags = 1 + data = length 3, hash 7724 + sample 44: time = 37658246200 flags = 1 - data = length 2, hash FFFFF698 - sample 23: + data = length 12, hash 882191BE + sample 45: + time = 37658262888 + flags = 1 + data = length 3, hash 7724 + sample 46: time = 37658279577 flags = 1 - data = length 2, hash 1F4 - sample 24: + data = length 12, hash 8B4886B1 + sample 47: + time = 37658296244 + flags = 1 + data = length 3, hash 7724 + sample 48: time = 37658312933 flags = 1 - data = length 2, hash FFFFF71B - sample 25: + data = length 12, hash 98D98F96 + sample 49: + time = 37658329622 + flags = 1 + data = length 3, hash 7724 + sample 50: time = 37658346311 flags = 1 - data = length 2, hash F91 - sample 26: + data = length 30, hash CF8E53E3 + sample 51: + time = 37658362977 + flags = 1 + data = length 6, hash 36289CE2 + sample 52: time = 37658379666 flags = 1 - data = length 2, hash 166 - sample 27: + data = length 12, hash F883C9EE + sample 53: + time = 37658396355 + flags = 1 + data = length 3, hash 7724 + sample 54: time = 37658413044 flags = 1 - data = length 2, hash 1023 - sample 28: + data = length 12, hash 6E6B2B9C + sample 55: + time = 37658429711 + flags = 1 + data = length 3, hash 7724 + sample 56: time = 37658446400 flags = 1 - data = length 2, hash 117A - sample 29: + data = length 12, hash B4FE7F08 + sample 57: + time = 37658463088 + flags = 1 + data = length 3, hash 7724 + sample 58: time = 37658479777 flags = 1 - data = length 2, hash 784 - sample 30: + data = length 12, hash 5A1EA7C7 + sample 59: + time = 37658496444 + flags = 1 + data = length 3, hash 7724 + sample 60: time = 37658513133 flags = 1 - data = length 2, hash 1F8 - sample 31: + data = length 12, hash 46BD6CC9 + sample 61: + time = 37658529822 + flags = 1 + data = length 3, hash 7724 + sample 62: time = 37658546511 flags = 1 - data = length 2, hash 10D9 - sample 32: + data = length 12, hash 1B1E2554 + sample 63: + time = 37658563177 + flags = 1 + data = length 3, hash 7724 + sample 64: time = 37658579866 flags = 1 - data = length 2, hash 935 - sample 33: + data = length 12, hash 91FCC537 + sample 65: + time = 37658596555 + flags = 1 + data = length 3, hash 7724 + sample 66: time = 37658613244 flags = 1 - data = length 2, hash 2B5 - sample 34: + data = length 12, hash A9355E1B + sample 67: + time = 37658629911 + flags = 1 + data = length 3, hash 7724 + sample 68: time = 37658646600 flags = 1 - data = length 2, hash F5 - sample 35: + data = length 12, hash 2511F69B + sample 69: + time = 37658663288 + flags = 1 + data = length 3, hash 7724 + sample 70: time = 37658679977 flags = 1 - data = length 2, hash FFFFF87A - sample 36: + data = length 12, hash 90925736 + sample 71: + time = 37658696644 + flags = 1 + data = length 3, hash 7724 + sample 72: time = 37658713333 flags = 1 - data = length 2, hash FFFFF698 - sample 37: + data = length 21, hash 431EEE30 + sample 73: + time = 37658730022 + flags = 1 + data = length 3, hash 7724 + sample 74: time = 37658746711 flags = 1 - data = length 2, hash 1F4 - sample 38: + data = length 12, hash 7BDEF631 + sample 75: + time = 37658763377 + flags = 1 + data = length 3, hash 7724 + sample 76: time = 37658780066 flags = 1 - data = length 2, hash 793 - sample 39: + data = length 12, hash A2EEF59E + sample 77: + time = 37658796755 + flags = 1 + data = length 3, hash 7724 + sample 78: time = 37658813444 flags = 1 - data = length 2, hash FF0 - sample 40: + data = length 12, hash BFC6C022 + sample 79: + time = 37658830111 + flags = 1 + data = length 3, hash 7724 + sample 80: time = 37658846800 flags = 1 - data = length 2, hash 16B - sample 41: + data = length 12, hash CD4D8FCA + sample 81: + time = 37658863488 + flags = 1 + data = length 3, hash 7724 + sample 82: time = 37658880177 flags = 1 - data = length 2, hash 2C0 - sample 42: + data = length 12, hash 2BDE8EFA + sample 83: + time = 37658896844 + flags = 1 + data = length 3, hash 7724 + sample 84: time = 37658913533 flags = 1 - data = length 2, hash FFFFF953 - sample 43: + data = length 12, hash 8C858812 + sample 85: + time = 37658930222 + flags = 1 + data = length 3, hash 7724 + sample 86: time = 37658946911 flags = 1 - data = length 2, hash FFFFF3C1 - sample 44: + data = length 12, hash DE7D0E31 + sample 87: + time = 37658963577 + flags = 1 + data = length 3, hash 7724 + sample 88: time = 37658980266 flags = 1 - data = length 2, hash FFFFF3C1 - sample 45: + data = length 3, hash 7363 + sample 89: + time = 37658996955 + flags = 1 + data = length 3, hash 7724 + sample 90: time = 37659013644 flags = 1 - data = length 2, hash FFFFF3C1 - sample 46: + data = length 3, hash 7363 + sample 91: + time = 37659030311 + flags = 1 + data = length 3, hash 7724 + sample 92: time = 37659047000 flags = 1 - data = length 2, hash FFFFF3C1 - sample 47: + data = length 3, hash 7363 + sample 93: + time = 37659063688 + flags = 1 + data = length 3, hash 7724 + sample 94: time = 37659080377 flags = 1 - data = length 2, hash FFFFF3C1 - sample 48: + data = length 3, hash 7363 + sample 95: + time = 37659097044 + flags = 1 + data = length 3, hash 7724 + sample 96: time = 37659113733 flags = 1 - data = length 2, hash FFFFF3C1 - sample 49: + data = length 3, hash 7363 + sample 97: + time = 37659130422 + flags = 1 + data = length 3, hash 7724 + sample 98: time = 37659147111 flags = 1 - data = length 2, hash FFFFF3C1 - sample 50: + data = length 3, hash 7363 + sample 99: + time = 37659163777 + flags = 1 + data = length 3, hash 7724 + sample 100: time = 37659180466 flags = 1 - data = length 2, hash FFFFF3C1 - sample 51: + data = length 3, hash 7363 + sample 101: + time = 37659197155 + flags = 1 + data = length 3, hash 7724 + sample 102: time = 37659213844 flags = 1 - data = length 2, hash FFFFF3C1 - sample 52: + data = length 3, hash 7363 + sample 103: + time = 37659230511 + flags = 1 + data = length 3, hash 7724 + sample 104: time = 37659247200 flags = 1 - data = length 2, hash FFFFF3C1 - sample 53: + data = length 3, hash 7363 + sample 105: + time = 37659263888 + flags = 1 + data = length 3, hash 7724 + sample 106: time = 37659280577 flags = 1 - data = length 2, hash FFFFF3C1 - sample 54: + data = length 3, hash 7363 + sample 107: + time = 37659297244 + flags = 1 + data = length 3, hash 7724 + sample 108: time = 37659313933 flags = 1 - data = length 2, hash FFFFF3C1 - sample 55: + data = length 3, hash 7363 + sample 109: + time = 37659330622 + flags = 1 + data = length 3, hash 7724 + sample 110: time = 37659347311 flags = 1 - data = length 2, hash FFFFF3C1 - sample 56: + data = length 3, hash 7363 + sample 111: + time = 37659363977 + flags = 1 + data = length 3, hash 7724 + sample 112: time = 37659380666 flags = 1 - data = length 2, hash FFFFF3C1 - sample 57: + data = length 3, hash 7363 + sample 113: + time = 37659397355 + flags = 1 + data = length 3, hash 7724 + sample 114: time = 37659414044 flags = 1 - data = length 2, hash FFFFF3C1 - sample 58: + data = length 3, hash 7363 + sample 115: + time = 37659430711 + flags = 1 + data = length 3, hash 7724 + sample 116: time = 37659447400 flags = 1 - data = length 2, hash FFFFF3C1 - sample 59: + data = length 3, hash 7363 + sample 117: + time = 37659464088 + flags = 1 + data = length 3, hash 7724 + sample 118: time = 37659480777 flags = 1 - data = length 2, hash FFFFF3C1 - sample 60: + data = length 3, hash 7363 + sample 119: + time = 37659497444 + flags = 1 + data = length 3, hash 7724 + sample 120: time = 37659514133 flags = 1 - data = length 2, hash FFFFF3C1 - sample 61: + data = length 3, hash 7363 + sample 121: + time = 37659530822 + flags = 1 + data = length 3, hash 7724 + sample 122: time = 37659547511 flags = 1 - data = length 2, hash FFFFF3C1 - sample 62: + data = length 3, hash 7363 + sample 123: + time = 37659564177 + flags = 1 + data = length 3, hash 7724 + sample 124: time = 37659580866 flags = 1 - data = length 2, hash FFFFF3C1 - sample 63: + data = length 3, hash 7363 + sample 125: + time = 37659597555 + flags = 1 + data = length 3, hash 7724 + sample 126: time = 37659614244 flags = 1 - data = length 2, hash FFFFF6CD - sample 64: + data = length 3, hash 766F + sample 127: + time = 37659630911 + flags = 1 + data = length 3, hash 7724 + sample 128: time = 37659647600 flags = 1 - data = length 2, hash FFFFF6DC - sample 65: + data = length 3, hash 767E + sample 129: + time = 37659664288 + flags = 1 + data = length 3, hash 7724 + sample 130: time = 37659680977 flags = 1 - data = length 2, hash FFFFF65B - sample 66: + data = length 15, hash 191B585A + sample 131: + time = 37659697644 + flags = 1 + data = length 3, hash 7724 + sample 132: time = 37659714333 flags = 1 - data = length 2, hash FFFFF6CD - sample 67: + data = length 12, hash 15EC5FC5 + sample 133: + time = 37659731022 + flags = 1 + data = length 3, hash 7724 + sample 134: time = 37659747711 flags = 1 - data = length 2, hash FFFFF6FF - sample 68: + data = length 3, hash 76A1 + sample 135: + time = 37659764377 + flags = 1 + data = length 3, hash 7724 + sample 136: time = 37659781066 flags = 1 - data = length 2, hash FFFFF6AC - sample 69: + data = length 30, hash E8012479 + sample 137: + time = 37659797755 + flags = 1 + data = length 6, hash 36289D5E + sample 138: time = 37659814444 flags = 1 - data = length 2, hash FFFFF5FE - sample 70: + data = length 12, hash D32F29F3 + sample 139: + time = 37659831111 + flags = 1 + data = length 3, hash 7724 + sample 140: time = 37659847800 flags = 1 - data = length 2, hash FFFFFEF7 - sample 71: + data = length 21, hash 6258623 + sample 141: + time = 37659864488 + flags = 1 + data = length 3, hash 7724 + sample 142: time = 37659881177 flags = 1 - data = length 2, hash 120C - sample 72: + data = length 12, hash FE69ABA2 + sample 143: + time = 37659897844 + flags = 1 + data = length 3, hash 7724 + sample 144: time = 37659914533 flags = 1 - data = length 2, hash 1124 - sample 73: + data = length 12, hash 958D0815 + sample 145: + time = 37659931222 + flags = 1 + data = length 3, hash 7724 + sample 146: time = 37659947911 flags = 1 - data = length 2, hash 1A9 - sample 74: + data = length 12, hash FF57BFD8 + sample 147: + time = 37659964577 + flags = 1 + data = length 3, hash 7724 + sample 148: time = 37659981266 flags = 1 - data = length 2, hash 935 + data = length 12, hash 922122E7 + sample 149: + time = 37659997955 + flags = 1 + data = length 3, hash 7724 tracksEnded = true diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index d0ba4d800c..dcab0c6275 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -33,14 +33,14 @@ public class DefaultExtractorInputTest extends TestCase { private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8}; private static final int LARGE_TEST_DATA_LENGTH = 8192; - public void testInitialPosition() throws IOException { + public void testInitialPosition() throws Exception { FakeDataSource testDataSource = buildDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET); assertEquals(123, input.getPosition()); } - public void testRead() throws IOException, InterruptedException { + public void testRead() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; // We expect to perform three reads of three bytes, as setup in buildTestDataSource. @@ -58,7 +58,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput); } - public void testReadPeeked() throws IOException, InterruptedException { + public void testReadPeeked() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -71,7 +71,7 @@ public class DefaultExtractorInputTest extends TestCase { assertTrue(Arrays.equals(TEST_DATA, target)); } - public void testReadMoreDataPeeked() throws IOException, InterruptedException { + public void testReadMoreDataPeeked() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -84,7 +84,7 @@ public class DefaultExtractorInputTest extends TestCase { assertTrue(Arrays.equals(TEST_DATA, target)); } - public void testReadFullyOnce() throws IOException, InterruptedException { + public void testReadFullyOnce() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; input.readFully(target, 0, TEST_DATA.length); @@ -103,7 +103,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testReadFullyTwice() throws IOException, InterruptedException { + public void testReadFullyTwice() throws Exception { // Read TEST_DATA in two parts. DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[5]; @@ -116,7 +116,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(5 + 4, input.getPosition()); } - public void testReadFullyTooMuch() throws IOException, InterruptedException { + public void testReadFullyTooMuch() throws Exception { // Read more than TEST_DATA. Should fail with an EOFException. Position should not update. DefaultExtractorInput input = createDefaultExtractorInput(); try { @@ -141,7 +141,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(0, input.getPosition()); } - public void testReadFullyWithFailingDataSource() throws IOException, InterruptedException { + public void testReadFullyWithFailingDataSource() throws Exception { FakeDataSource testDataSource = buildFailingDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); try { @@ -155,7 +155,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(0, input.getPosition()); } - public void testReadFullyHalfPeeked() throws IOException, InterruptedException { + public void testReadFullyHalfPeeked() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -168,7 +168,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(TEST_DATA.length, input.getPosition()); } - public void testSkip() throws IOException, InterruptedException { + public void testSkip() throws Exception { FakeDataSource testDataSource = buildDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); // We expect to perform three skips of three bytes, as setup in buildTestDataSource. @@ -180,7 +180,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(C.RESULT_END_OF_INPUT, expectedEndOfInput); } - public void testLargeSkip() throws IOException, InterruptedException { + public void testLargeSkip() throws Exception { FakeDataSource testDataSource = buildLargeDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); // Check that skipping the entire data source succeeds. @@ -190,7 +190,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testSkipFullyOnce() throws IOException, InterruptedException { + public void testSkipFullyOnce() throws Exception { // Skip TEST_DATA. DefaultExtractorInput input = createDefaultExtractorInput(); input.skipFully(TEST_DATA.length); @@ -207,7 +207,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testSkipFullyTwice() throws IOException, InterruptedException { + public void testSkipFullyTwice() throws Exception { // Skip TEST_DATA in two parts. DefaultExtractorInput input = createDefaultExtractorInput(); input.skipFully(5); @@ -216,7 +216,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(5 + 4, input.getPosition()); } - public void testSkipFullyTwicePeeked() throws IOException, InterruptedException { + public void testSkipFullyTwicePeeked() throws Exception { // Skip TEST_DATA. DefaultExtractorInput input = createDefaultExtractorInput(); @@ -230,7 +230,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(TEST_DATA.length, input.getPosition()); } - public void testSkipFullyTooMuch() throws IOException, InterruptedException { + public void testSkipFullyTooMuch() throws Exception { // Skip more than TEST_DATA. Should fail with an EOFException. Position should not update. DefaultExtractorInput input = createDefaultExtractorInput(); try { @@ -253,7 +253,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(0, input.getPosition()); } - public void testSkipFullyWithFailingDataSource() throws IOException, InterruptedException { + public void testSkipFullyWithFailingDataSource() throws Exception { FakeDataSource testDataSource = buildFailingDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); try { @@ -266,7 +266,7 @@ public class DefaultExtractorInputTest extends TestCase { assertEquals(0, input.getPosition()); } - public void testSkipFullyLarge() throws IOException, InterruptedException { + public void testSkipFullyLarge() throws Exception { // Tests skipping an amount of data that's larger than any internal scratch space. int largeSkipSize = 1024 * 1024; FakeDataSource.Builder builder = new FakeDataSource.Builder(); @@ -286,7 +286,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testPeekFully() throws IOException, InterruptedException { + public void testPeekFully() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; input.peekFully(target, 0, TEST_DATA.length); @@ -312,7 +312,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testResetPeekPosition() throws IOException, InterruptedException { + public void testResetPeekPosition() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; input.peekFully(target, 0, TEST_DATA.length); @@ -336,8 +336,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() - throws IOException, InterruptedException { + public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -348,8 +347,24 @@ public class DefaultExtractorInputTest extends TestCase { assertFalse(input.peekFully(target, 0, 1, true)); } - public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() - throws IOException, InterruptedException { + public void testPeekFullyAtEndThenReadEndOfInput() throws Exception { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length]; + + // Peek up to the end of the input. + assertTrue(input.peekFully(target, 0, TEST_DATA.length, false)); + + // Peek the end of the input. + assertFalse(input.peekFully(target, 0, 1, true)); + + // Read up to the end of the input. + assertTrue(input.readFully(target, 0, TEST_DATA.length, false)); + + // Read the end of the input. + assertFalse(input.readFully(target, 0, 1, true)); + } + + public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -365,8 +380,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() - throws IOException, InterruptedException { + public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); byte[] target = new byte[TEST_DATA.length]; @@ -382,7 +396,7 @@ public class DefaultExtractorInputTest extends TestCase { } } - private static FakeDataSource buildDataSource() throws IOException { + private static FakeDataSource buildDataSource() throws Exception { FakeDataSource.Builder builder = new FakeDataSource.Builder(); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)); @@ -392,7 +406,7 @@ public class DefaultExtractorInputTest extends TestCase { return testDataSource; } - private static FakeDataSource buildFailingDataSource() throws IOException { + private static FakeDataSource buildFailingDataSource() throws Exception { FakeDataSource.Builder builder = new FakeDataSource.Builder(); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6)); builder.appendReadError(new IOException()); @@ -402,7 +416,7 @@ public class DefaultExtractorInputTest extends TestCase { return testDataSource; } - private static FakeDataSource buildLargeDataSource() throws IOException { + private static FakeDataSource buildLargeDataSource() throws Exception { FakeDataSource.Builder builder = new FakeDataSource.Builder(); builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); FakeDataSource testDataSource = builder.build(); @@ -410,8 +424,9 @@ public class DefaultExtractorInputTest extends TestCase { return testDataSource; } - private static DefaultExtractorInput createDefaultExtractorInput() throws IOException { + private static DefaultExtractorInput createDefaultExtractorInput() throws Exception { FakeDataSource testDataSource = buildDataSource(); return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); } + } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java index e268d9f12d..c70710f1ee 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java @@ -33,4 +33,13 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase { }, "mp3/bear.mp3", getInstrumentation()); } + public void testTrimmedMp3Sample() throws Exception { + TestUtil.assertOutput(new TestUtil.ExtractorFactory() { + @Override + public Extractor create() { + return new Mp3Extractor(); + } + }, "mp3/play-trimmed.mp3", getInstrumentation()); + } + } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 083ade6ac5..b149b66feb 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -16,8 +16,16 @@ package com.google.android.exoplayer2.extractor.ts; import android.test.InstrumentationTestCase; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.PositionHolder; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import com.google.android.exoplayer2.testutil.FakeExtractorOutput; +import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Random; @@ -61,6 +69,31 @@ public final class TsExtractorTest extends InstrumentationTestCase { }, "ts/sample.ts", fileData, getInstrumentation()); } + public void testCustomPesReader() throws Exception { + CustomEsReaderFactory factory = new CustomEsReaderFactory(); + TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory); + FakeExtractorInput input = new FakeExtractorInput.Builder() + .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) + .setSimulateIOErrors(false) + .setSimulateUnknownLength(false) + .setSimulatePartialReads(false).build(); + FakeExtractorOutput output = new FakeExtractorOutput(); + tsExtractor.init(output); + tsExtractor.seek(input.getPosition()); + PositionHolder seekPositionHolder = new PositionHolder(); + int readResult = Extractor.RESULT_CONTINUE; + while (readResult != Extractor.RESULT_END_OF_INPUT) { + readResult = tsExtractor.read(input, seekPositionHolder); + } + CustomEsReader reader = factory.reader; + assertEquals(2, reader.packetsRead); + TrackOutput trackOutput = reader.getTrackOutput(); + assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)); + assertEquals( + Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0), + ((FakeTrackOutput) trackOutput).format); + } + private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException { for (int i = 0; i < length; i++) { if (((byte) i) == TS_SYNC_BYTE) { @@ -71,4 +104,62 @@ public final class TsExtractorTest extends InstrumentationTestCase { } } + private static final class CustomEsReader extends ElementaryStreamReader { + + public int packetsRead = 0; + + public CustomEsReader(TrackOutput output, String language) { + super(output); + output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, + language, null, 0)); + } + + @Override + public void seek() { + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + } + + @Override + public void consume(ParsableByteArray data) { + } + + @Override + public void packetFinished() { + packetsRead++; + } + + public TrackOutput getTrackOutput() { + return output; + } + + } + + private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory { + + private final ElementaryStreamReader.Factory defaultFactory; + private CustomEsReader reader; + + public CustomEsReaderFactory() { + defaultFactory = new DefaultStreamReaderFactory(); + } + + @Override + public ElementaryStreamReader onPmtEntry(int pid, int streamType, + ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) { + if (streamType == 3) { + // We need to manually avoid a duplicate custom reader creation. + if (reader == null) { + reader = new CustomEsReader(output.track(pid), esInfo.language); + } + return reader; + } else { + return defaultFactory.onPmtEntry(pid, streamType, esInfo, output); + } + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/C.java b/library/src/main/java/com/google/android/exoplayer2/C.java index 85813b391a..8c69524e95 100644 --- a/library/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/src/main/java/com/google/android/exoplayer2/C.java @@ -17,8 +17,11 @@ package com.google.android.exoplayer2; import android.media.AudioFormat; import android.media.MediaCodec; +import android.support.annotation.IntDef; import android.view.Surface; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.UUID; /** @@ -70,55 +73,79 @@ public final class C { */ public static final String UTF8_NAME = "UTF-8"; + /** + * Crypto modes for a codec. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) + public @interface CryptoMode {} + /** + * @see MediaCodec#CRYPTO_MODE_UNENCRYPTED + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED; /** * @see MediaCodec#CRYPTO_MODE_AES_CTR */ @SuppressWarnings("InlinedApi") public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; + /** + * @see MediaCodec#CRYPTO_MODE_AES_CBC + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC; + /** + * Represents an audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS, + ENCODING_DTS_HD}) + public @interface Encoding {} + + /** + * Represents a PCM audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT}) + public @interface PcmEncoding {} /** * @see AudioFormat#ENCODING_INVALID */ public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; - /** * @see AudioFormat#ENCODING_PCM_8BIT */ public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; - /** * @see AudioFormat#ENCODING_PCM_16BIT */ public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; - /** * PCM encoding with 24 bits per sample. */ public static final int ENCODING_PCM_24BIT = 0x80000000; - /** * PCM encoding with 32 bits per sample. */ public static final int ENCODING_PCM_32BIT = 0x40000000; - /** * @see AudioFormat#ENCODING_AC3 */ @SuppressWarnings("InlinedApi") public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; - /** * @see AudioFormat#ENCODING_E_AC3 */ @SuppressWarnings("InlinedApi") public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; - /** * @see AudioFormat#ENCODING_DTS */ @SuppressWarnings("InlinedApi") public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; - /** * @see AudioFormat#ENCODING_DTS_HD */ @@ -132,48 +159,93 @@ public final class C { public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23 ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + /** + * Flags which can apply to a buffer containing a media sample. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, + BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY}) + public @interface BufferFlags {} /** * Indicates that a buffer holds a synchronization sample. */ @SuppressWarnings("InlinedApi") public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; - /** * Flag for empty buffers that signal that the end of the stream was reached. */ @SuppressWarnings("InlinedApi") public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; - /** * Indicates that a buffer is (at least partially) encrypted. */ public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000; - /** * Indicates that a buffer should be decoded but not rendered. */ public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000; + /** + * Track selection flags. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, + SELECTION_FLAG_AUTOSELECT}) + public @interface SelectionFlags {} + /** + * Indicates that the track should be selected if user preferences do not state otherwise. + */ + public static final int SELECTION_FLAG_DEFAULT = 1; + /** + * Indicates that the track must be displayed. Only applies to text tracks. + */ + public static final int SELECTION_FLAG_FORCED = 2; + /** + * Indicates that the player may choose to play the track in absence of an explicit user + * preference. + */ + public static final int SELECTION_FLAG_AUTOSELECT = 4; + + /** + * Represents a streaming or other media type. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) + public @interface ContentType {} + /** + * Value returned by {@link Util#inferContentType(String)} for DASH manifests. + */ + public static final int TYPE_DASH = 0; + /** + * Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. + */ + public static final int TYPE_SS = 1; + /** + * Value returned by {@link Util#inferContentType(String)} for HLS manifests. + */ + public static final int TYPE_HLS = 2; + /** + * Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or + * Smooth Streaming manifests. + */ + public static final int TYPE_OTHER = 3; + /** * A return value for methods where the end of an input was encountered. */ public static final int RESULT_END_OF_INPUT = -1; - /** * A return value for methods where the length of parsed data exceeds the maximum length allowed. */ public static final int RESULT_MAX_LENGTH_EXCEEDED = -2; - /** * A return value for methods where nothing was read. */ public static final int RESULT_NOTHING_READ = -3; - /** * A return value for methods where a buffer was read. */ public static final int RESULT_BUFFER_READ = -4; - /** * A return value for methods where a format was read. */ @@ -183,32 +255,26 @@ public final class C { * A data type constant for data of unknown or unspecified type. */ public static final int DATA_TYPE_UNKNOWN = 0; - /** * A data type constant for media, typically containing media samples. */ public static final int DATA_TYPE_MEDIA = 1; - /** * A data type constant for media, typically containing only initialization data. */ public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2; - /** * A data type constant for drm or encryption data. */ public static final int DATA_TYPE_DRM = 3; - /** * A data type constant for a manifest file. */ public static final int DATA_TYPE_MANIFEST = 4; - /** * A data type constant for time synchronization data. */ public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; - /** * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or * equal to this value. @@ -219,32 +285,26 @@ public final class C { * A type constant for tracks of unknown type. */ public static final int TRACK_TYPE_UNKNOWN = -1; - /** * A type constant for tracks of some default type, where the type itself is unknown. */ public static final int TRACK_TYPE_DEFAULT = 0; - /** * A type constant for audio tracks. */ public static final int TRACK_TYPE_AUDIO = 1; - /** * A type constant for video tracks. */ public static final int TRACK_TYPE_VIDEO = 2; - /** * A type constant for text tracks. */ public static final int TRACK_TYPE_TEXT = 3; - /** * A type constant for metadata tracks. */ public static final int TRACK_TYPE_METADATA = 4; - /** * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or * equal to this value. @@ -255,27 +315,22 @@ public final class C { * A selection reason constant for selections whose reasons are unknown or unspecified. */ public static final int SELECTION_REASON_UNKNOWN = 0; - /** * A selection reason constant for an initial track selection. */ public static final int SELECTION_REASON_INITIAL = 1; - /** * A selection reason constant for an manual (i.e. user initiated) track selection. */ public static final int SELECTION_REASON_MANUAL = 2; - /** * A selection reason constant for an adaptive track selection. */ public static final int SELECTION_REASON_ADAPTIVE = 3; - /** * A selection reason constant for a trick play track selection. */ public static final int SELECTION_REASON_TRICK_PLAY = 4; - /** * Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than * or equal to this value. @@ -363,16 +418,20 @@ public final class C { */ public static final int MSG_CUSTOM_BASE = 10000; + /** + * The stereo mode for 360/3D/VR videos. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT}) + public @interface StereoMode {} /** * Indicates Monoscopic stereo layout, used with 360/3D/VR videos. */ public static final int STEREO_MODE_MONO = 0; - /** * Indicates Top-Bottom stereo layout, used with 360/3D/VR videos. */ public static final int STEREO_MODE_TOP_BOTTOM = 1; - /** * Indicates Left-Right stereo layout, used with 360/3D/VR videos. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index a24a2363a2..1416dd0ae4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.trackselection.TrackSelections; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.Util; @@ -106,7 +106,7 @@ public final class DefaultLoadControl implements LoadControl { @Override public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections) { + TrackSelections trackSelections) { targetBufferSize = 0; for (int i = 0; i < renderers.length; i++) { if (trackSelections.get(i) != null) { diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index 8f55451712..72ac72e981 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -15,15 +15,24 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Thrown when a non-recoverable playback failure occurs. */ public final class ExoPlaybackException extends Exception { + /** + * The type of source that produced the error. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) + public @interface Type {} /** * The error occurred loading data from a {@link MediaSource}. *

@@ -47,6 +56,7 @@ public final class ExoPlaybackException extends Exception { * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and * {@link #TYPE_UNEXPECTED}. */ + @Type public final int type; /** @@ -85,7 +95,8 @@ public final class ExoPlaybackException extends Exception { return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET); } - private ExoPlaybackException(int type, String message, Throwable cause, int rendererIndex) { + private ExoPlaybackException(@Type int type, String message, Throwable cause, + int rendererIndex) { super(message, cause); this.type = type; this.rendererIndex = rendererIndex; diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index f4b21aac04..6ced2315f9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -243,16 +243,8 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline == null || pendingSeekAcks > 0) { return maskingWindowPositionMs; } else { - int periodIndex = playbackInfo.periodIndex; - timeline.getPeriod(periodIndex, period); - int windowIndex = period.windowIndex; - timeline.getWindow(windowIndex, window); - if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex - && window.getPositionInFirstPeriodUs() == 0 - && window.getDurationUs() == period.getDurationUs()) { - return C.usToMs(playbackInfo.bufferedPositionUs); - } - return getCurrentPosition(); + timeline.getPeriod(playbackInfo.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 8cb15a0271..2a9360337a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -27,7 +27,7 @@ import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.trackselection.TrackSelections; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaClock; @@ -40,8 +40,8 @@ import java.io.IOException; /** * Implements the internal behavior of {@link ExoPlayerImpl}. */ -/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback, - TrackSelector.InvalidationListener, MediaSource.Listener { +/* package */ final class ExoPlayerImplInternal implements Handler.Callback, + MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener { /** * Playback position information which is read on the application's thread by @@ -100,7 +100,7 @@ import java.io.IOException; private final Renderer[] renderers; private final RendererCapabilities[] rendererCapabilities; - private final TrackSelector trackSelector; + private final TrackSelector trackSelector; private final LoadControl loadControl; private final StandaloneMediaClock standaloneMediaClock; private final Handler handler; @@ -128,13 +128,13 @@ import java.io.IOException; private boolean isTimelineReady; private boolean isTimelineEnded; private int bufferAheadPeriodCount; - private MediaPeriodHolder playingPeriodHolder; - private MediaPeriodHolder readingPeriodHolder; - private MediaPeriodHolder loadingPeriodHolder; + private MediaPeriodHolder playingPeriodHolder; + private MediaPeriodHolder readingPeriodHolder; + private MediaPeriodHolder loadingPeriodHolder; private Timeline timeline; - public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, + public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, boolean playWhenReady, Handler eventHandler, PlaybackInfo playbackInfo) { this.renderers = renderers; @@ -458,12 +458,11 @@ import java.io.IOException; startRenderers(); } } - } else if (state == ExoPlayer.STATE_READY) { - if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady) { - rebuffering = playWhenReady; - setState(ExoPlayer.STATE_BUFFERING); - stopRenderers(); - } + } else if (state == ExoPlayer.STATE_READY + && (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady)) { + rebuffering = playWhenReady; + setState(ExoPlayer.STATE_BUFFERING); + stopRenderers(); } if (state == ExoPlayer.STATE_BUFFERING) { @@ -530,17 +529,17 @@ import java.io.IOException; rebuffering = false; setState(ExoPlayer.STATE_BUFFERING); - if (periodPositionUs == C.TIME_UNSET - || (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index - || (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) { + if (periodPositionUs == C.TIME_UNSET || (readingPeriodHolder != playingPeriodHolder + && (periodIndex == playingPeriodHolder.index + || periodIndex == readingPeriodHolder.index))) { // Clear the timeline because either the seek position is not known, or a renderer is reading // ahead to the next period and the seek is to either the playing or reading period. periodIndex = C.INDEX_UNSET; } // Clear the timeline, but keep the requested period if it is already prepared. - MediaPeriodHolder periodHolder = playingPeriodHolder; - MediaPeriodHolder newPlayingPeriodHolder = null; + MediaPeriodHolder periodHolder = playingPeriodHolder; + MediaPeriodHolder newPlayingPeriodHolder = null; while (periodHolder != null) { if (periodHolder.index == periodIndex && periodHolder.prepared) { newPlayingPeriodHolder = periodHolder; @@ -672,7 +671,7 @@ import java.io.IOException; return; } // Reselect tracks on each period in turn, until the selection changes. - MediaPeriodHolder periodHolder = playingPeriodHolder; + MediaPeriodHolder periodHolder = playingPeriodHolder; boolean selectionsChangedForReadPeriod = true; while (true) { if (periodHolder == null || !periodHolder.prepared) { @@ -691,16 +690,14 @@ import java.io.IOException; } if (selectionsChangedForReadPeriod) { - // Release everything after the playing period because a renderer may have read data from a - // track whose selection has now changed. + // Update streams and rebuffer for the new selection, recreating all streams if reading ahead. + boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; releasePeriodHoldersFrom(playingPeriodHolder.next); playingPeriodHolder.next = null; readingPeriodHolder = playingPeriodHolder; loadingPeriodHolder = playingPeriodHolder; bufferAheadPeriodCount = 0; - // Update streams for the new selection, recreating all streams if reading ahead. - boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; boolean[] streamResetFlags = new boolean[renderers.length]; long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags); @@ -739,7 +736,7 @@ import java.io.IOException; } } } - trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData); + trackSelector.onSelectionActivated(playingPeriodHolder.trackSelections); enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } else { // Release and re-prepare/buffer periods after the one whose selection changed. @@ -811,11 +808,11 @@ import java.io.IOException; playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window), index); - MediaPeriodHolder previousPeriod = playingPeriodHolder; + MediaPeriodHolder previousPeriodHolder = playingPeriodHolder; boolean seenReadingPeriod = false; bufferAheadPeriodCount = 0; - while (previousPeriod.next != null) { - MediaPeriodHolder periodHolder = previousPeriod.next; + while (previousPeriodHolder.next != null) { + MediaPeriodHolder periodHolder = previousPeriodHolder.next; index++; timeline.getPeriod(index, period, true); if (!periodHolder.uid.equals(period.uid)) { @@ -836,7 +833,7 @@ import java.io.IOException; } // Update the loading period to be the latest period that is still valid. - loadingPeriodHolder = previousPeriod; + loadingPeriodHolder = previousPeriodHolder; loadingPeriodHolder.next = null; // Release the rest of the timeline. @@ -850,7 +847,7 @@ import java.io.IOException; if (periodHolder == readingPeriodHolder) { seenReadingPeriod = true; } - previousPeriod = periodHolder; + previousPeriodHolder = periodHolder; } } else if (loadingPeriodHolder != null) { Object uid = loadingPeriodHolder.uid; @@ -953,10 +950,12 @@ import java.io.IOException; periodStartPositionUs = defaultPosition.second; } Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid; - MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, + MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, loadControl.getAllocator(), periodStartPositionUs); - MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, - trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs); + newMediaPeriod.prepare(this); + MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder<>(renderers, + rendererCapabilities, trackSelector, mediaSource, newMediaPeriod, newPeriodUid, + periodStartPositionUs); timeline.getWindow(windowIndex, window); newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex); if (loadingPeriodHolder != null) { @@ -995,19 +994,24 @@ import java.io.IOException; eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); } updateTimelineState(); - if (readingPeriodHolder == null) { + + if (readingPeriodHolder.isLast) { // The renderers have their final SampleStreams. + for (Renderer renderer : enabledRenderers) { + renderer.setCurrentStreamIsFinal(); + } return; } + for (Renderer renderer : enabledRenderers) { if (!renderer.hasReadStreamToEnd()) { return; } } if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { - TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections; + TrackSelections oldTrackSelections = readingPeriodHolder.trackSelections; readingPeriodHolder = readingPeriodHolder.next; - TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections; + TrackSelections newTrackSelections = readingPeriodHolder.trackSelections; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; TrackSelection oldSelection = oldTrackSelections.get(i); @@ -1029,11 +1033,6 @@ import java.io.IOException; } } } - } else if (readingPeriodHolder.isLast) { - readingPeriodHolder = null; - for (Renderer renderer : enabledRenderers) { - renderer.setCurrentStreamIsFinal(); - } } } @@ -1071,7 +1070,7 @@ import java.io.IOException; long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs(); if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { long loadingPeriodPositionUs = rendererPositionUs - - loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs; + - loadingPeriodHolder.rendererPositionOffsetUs; long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); setIsLoading(continueLoading); @@ -1086,14 +1085,15 @@ import java.io.IOException; } } - private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { + private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { while (periodHolder != null) { periodHolder.release(); periodHolder = periodHolder.next; } } - private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { + private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) + throws ExoPlaybackException { int enabledRendererCount = 0; boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; for (int i = 0; i < renderers.length; i++) { @@ -1116,7 +1116,7 @@ import java.io.IOException; } } - trackSelector.onSelectionActivated(periodHolder.trackSelectionData); + trackSelector.onSelectionActivated(periodHolder.trackSelections); playingPeriodHolder = periodHolder; enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } @@ -1173,7 +1173,7 @@ import java.io.IOException; /** * Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */ - private static final class MediaPeriodHolder { + private static final class MediaPeriodHolder { public final MediaPeriod mediaPeriod; public final Object uid; @@ -1187,21 +1187,20 @@ import java.io.IOException; public boolean prepared; public boolean hasEnabledTracks; public long rendererPositionOffsetUs; - public MediaPeriodHolder next; + public MediaPeriodHolder next; public boolean needsContinueLoading; private final Renderer[] renderers; private final RendererCapabilities[] rendererCapabilities; - private final TrackSelector trackSelector; + private final TrackSelector trackSelector; private final MediaSource mediaSource; - private Object trackSelectionData; - private TrackSelectionArray trackSelections; - private TrackSelectionArray periodTrackSelections; + private TrackSelections trackSelections; + private TrackSelections periodTrackSelections; public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, - TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid, - long positionUs) { + TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, + Object uid, long positionUs) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.trackSelector = trackSelector; @@ -1213,7 +1212,7 @@ import java.io.IOException; startPositionUs = positionUs; } - public void setNext(MediaPeriodHolder next) { + public void setNext(MediaPeriodHolder next) { this.next = next; } @@ -1235,14 +1234,12 @@ import java.io.IOException; } public boolean selectTracks() throws ExoPlaybackException { - Pair result = - trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups()); - TrackSelectionArray newTrackSelections = result.first; + TrackSelections newTrackSelections = trackSelector.selectTracks(rendererCapabilities, + mediaPeriod.getTrackGroups()); if (newTrackSelections.equals(periodTrackSelections)) { return false; } trackSelections = newTrackSelections; - trackSelectionData = result.second; return true; } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index e2e17c425c..1e95372730 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { /** * The version of the library, expressed as a string. */ - String VERSION = "2.0.0"; + String VERSION = "2.0.1"; /** * The version of the library, expressed as an integer. @@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo { * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * integer version 123045006 (123-045-006). */ - int VERSION_INT = 2000000; + int VERSION_INT = 2000001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} diff --git a/library/src/main/java/com/google/android/exoplayer2/Format.java b/library/src/main/java/com/google/android/exoplayer2/Format.java index 9cfe019ef4..550e6ab1d8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/src/main/java/com/google/android/exoplayer2/Format.java @@ -39,20 +39,6 @@ public final class Format implements Parcelable { */ public static final int NO_VALUE = -1; - /** - * Indicates that the track should be selected if user preferences do not state otherwise. - */ - public static final int SELECTION_FLAG_DEFAULT = 1; - /** - * Indicates that the track must be displayed. Only applies to text tracks. - */ - public static final int SELECTION_FLAG_FORCED = 2; - /** - * Indicates that the player may choose to play the track in absence of an explicit user - * preference. - */ - public static final int SELECTION_FLAG_AUTOSELECT = 4; - /** * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to * the timestamps of their parent samples. @@ -131,6 +117,7 @@ public final class Format implements Parcelable { * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link * C#STEREO_MODE_LEFT_RIGHT}. */ + @C.StereoMode public final int stereoMode; /** * The projection data for 360/VR video, or null if not applicable. @@ -153,6 +140,7 @@ public final class Format implements Parcelable { * {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for * other media types. */ + @C.PcmEncoding public final int pcmEncoding; /** * The number of samples to trim from the start of the decoded audio stream. @@ -177,6 +165,7 @@ public final class Format implements Parcelable { /** * Track selection flags. */ + @C.SelectionFlags public final int selectionFlags; /** @@ -218,7 +207,7 @@ public final class Format implements Parcelable { public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, List initializationData, int rotationDegrees, float pixelWidthHeightRatio, - byte[] projectionData, int stereoMode, DrmInitData drmInitData) { + byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData, @@ -229,7 +218,7 @@ public final class Format implements Parcelable { public static Format createAudioContainerFormat(String id, String containerMimeType, String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, - List initializationData, int selectionFlags, String language) { + List initializationData, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData, @@ -238,25 +227,26 @@ public final class Format implements Parcelable { public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int channelCount, int sampleRate, - List initializationData, DrmInitData drmInitData, int selectionFlags, - String language) { + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language); } public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, - int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, - List initializationData, DrmInitData drmInitData, int selectionFlags, - String language) { + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, selectionFlags, language); } public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, - int bitrate, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, - int encoderDelay, int encoderPadding, List initializationData, - DrmInitData drmInitData, int selectionFlags, String language) { + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding, + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, @@ -266,20 +256,21 @@ public final class Format implements Parcelable { // Text. public static Format createTextContainerFormat(String id, String containerMimeType, - String sampleMimeType, String codecs, int bitrate, int selectionFlags, String language) { + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, - int bitrate, int selectionFlags, String language, DrmInitData drmInitData) { + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, drmInitData, OFFSET_SAMPLE_RELATIVE); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, - int bitrate, int selectionFlags, String language, DrmInitData drmInitData, + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData, long subsampleOffsetUs) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, @@ -313,10 +304,10 @@ public final class Format implements Parcelable { /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, - float pixelWidthHeightRatio, byte[] projectionData, int stereoMode, int channelCount, - int sampleRate, int pcmEncoding, int encoderDelay, int encoderPadding, int selectionFlags, - String language, long subsampleOffsetUs, List initializationData, - DrmInitData drmInitData) { + float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, + int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, + int encoderPadding, @C.SelectionFlags int selectionFlags, String language, + long subsampleOffsetUs, List initializationData, DrmInitData drmInitData) { this.id = id; this.containerMimeType = containerMimeType; this.sampleMimeType = sampleMimeType; @@ -343,6 +334,7 @@ public final class Format implements Parcelable { this.drmInitData = drmInitData; } + @SuppressWarnings("ResourceType") /* package */ Format(Parcel in) { id = in.readString(); containerMimeType = in.readString(); @@ -388,8 +380,8 @@ public final class Format implements Parcelable { selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData); } - public Format copyWithContainerInfo(String id, int bitrate, int width, int height, - int selectionFlags, String language) { + public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, + @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, @@ -402,7 +394,7 @@ public final class Format implements Parcelable { String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; - int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; + @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; String language = this.language == null ? manifestFormat.language : this.language; DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null) || this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; diff --git a/library/src/main/java/com/google/android/exoplayer2/LoadControl.java b/library/src/main/java/com/google/android/exoplayer2/LoadControl.java index 213d5e5297..7842ada276 100644 --- a/library/src/main/java/com/google/android/exoplayer2/LoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer2/LoadControl.java @@ -17,8 +17,7 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.trackselection.TrackSelections; import com.google.android.exoplayer2.upstream.Allocator; /** @@ -31,10 +30,10 @@ public interface LoadControl { * * @param renderers The renderers. * @param trackGroups The {@link TrackGroup}s from which the selection was made. - * @param trackSelections The {@link TrackSelection}s that were made. + * @param trackSelections The track selections that were made. */ void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, - TrackSelectionArray trackSelections); + TrackSelections trackSelections); /** * Called by the player when all tracks are disabled. diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 07702c8f8c..5eda502da5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; @@ -40,6 +41,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Frame; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.trackselection.TrackSelections; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -80,18 +82,14 @@ public final class SimpleExoPlayer implements ExoPlayer { /** * Called when a frame is rendered for the first time since setting the surface, and when a - * frame is rendered for the first time since the renderer was reset. - * - * @param surface The {@link Surface} to which a first frame has been rendered. + * frame is rendered for the first time since a video track was selected. */ - void onRenderedFirstFrame(Surface surface); + void onRenderedFirstFrame(); /** - * Called when the renderer is disabled. - * - * @param counters {@link DecoderCounters} that were updated by the renderer. + * Called when a video track is no longer selected. */ - void onVideoDisabled(DecoderCounters counters); + void onVideoTracksDisabled(); } @@ -105,9 +103,11 @@ public final class SimpleExoPlayer implements ExoPlayer { private final int videoRendererCount; private final int audioRendererCount; + private boolean videoTracksEnabled; private Format videoFormat; private Format audioFormat; + private Surface surface; private SurfaceHolder surfaceHolder; private TextureView textureView; private TextRenderer.Output textOutput; @@ -121,11 +121,12 @@ public final class SimpleExoPlayer implements ExoPlayer { private float volume; private PlaybackParamsHolder playbackParamsHolder; - /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, - LoadControl loadControl, DrmSessionManager drmSessionManager, + /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { mainHandler = new Handler(); componentListener = new ComponentListener(); + trackSelector.addListener(componentListener); // Build the renderers. ArrayList renderersList = new ArrayList<>(); @@ -509,8 +510,9 @@ public final class SimpleExoPlayer implements ExoPlayer { // Internal methods. - private void buildRenderers(Context context, DrmSessionManager drmSessionManager, - ArrayList renderersList, long allowedVideoJoiningTimeMs) { + private void buildRenderers(Context context, + DrmSessionManager drmSessionManager, ArrayList renderersList, + long allowedVideoJoiningTimeMs) { MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener, @@ -601,6 +603,7 @@ public final class SimpleExoPlayer implements ExoPlayer { } private void setVideoSurfaceInternal(Surface surface) { + this.surface = surface; ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; int count = 0; for (Renderer renderer : renderers) { @@ -618,7 +621,8 @@ public final class SimpleExoPlayer implements ExoPlayer { private final class ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output>, - SurfaceHolder.Callback, TextureView.SurfaceTextureListener { + SurfaceHolder.Callback, TextureView.SurfaceTextureListener, + TrackSelector.EventListener { // VideoRendererEventListener implementation @@ -669,8 +673,8 @@ public final class SimpleExoPlayer implements ExoPlayer { @Override public void onRenderedFirstFrame(Surface surface) { - if (videoListener != null) { - videoListener.onRenderedFirstFrame(surface); + if (videoListener != null && SimpleExoPlayer.this.surface == surface) { + videoListener.onRenderedFirstFrame(); } if (videoDebugListener != null) { videoDebugListener.onRenderedFirstFrame(surface); @@ -679,9 +683,6 @@ public final class SimpleExoPlayer implements ExoPlayer { @Override public void onVideoDisabled(DecoderCounters counters) { - if (videoListener != null) { - videoListener.onVideoDisabled(counters); - } if (videoDebugListener != null) { videoDebugListener.onVideoDisabled(counters); } @@ -800,6 +801,23 @@ public final class SimpleExoPlayer implements ExoPlayer { // Do nothing. } + // TrackSelector.EventListener implementation + + @Override + public void onTrackSelectionsChanged(TrackSelections trackSelections) { + boolean videoTracksEnabled = false; + for (int i = 0; i < renderers.length; i++) { + if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelections.get(i) != null) { + videoTracksEnabled = true; + break; + } + } + if (videoListener != null && SimpleExoPlayer.this.videoTracksEnabled && !videoTracksEnabled) { + videoListener.onVideoTracksDisabled(); + } + SimpleExoPlayer.this.videoTracksEnabled = videoTracksEnabled; + } + } @TargetApi(23) diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index 02fa2b8f85..87f6546e1f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -208,7 +208,9 @@ public final class AudioTrack { private android.media.AudioTrack audioTrack; private int sampleRate; private int channelConfig; + @C.Encoding private int sourceEncoding; + @C.Encoding private int targetEncoding; private boolean passthrough; private int pcmFrameSize; @@ -348,8 +350,8 @@ public final class AudioTrack { * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * suitable buffer size automatically. */ - public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding, - int specifiedBufferSize) { + public void configure(String mimeType, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) { int channelConfig; switch (channelCount) { case 1: @@ -381,7 +383,7 @@ public final class AudioTrack { } boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); - int sourceEncoding; + @C.Encoding int sourceEncoding; if (passthrough) { sourceEncoding = getEncodingForMimeType(mimeType); } else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT @@ -470,7 +472,7 @@ public final class AudioTrack { if (keepSessionIdAudioTrack == null) { int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. int channelConfig = AudioFormat.CHANNEL_OUT_MONO; - int encoding = C.ENCODING_PCM_16BIT; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); @@ -962,7 +964,7 @@ public final class AudioTrack { * @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the * capacity was insufficient for the output. */ - private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int sourceEncoding, + private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding, ByteBuffer out) { int offset = buffer.position(); int limit = buffer.limit(); @@ -1023,6 +1025,7 @@ public final class AudioTrack { return resampledBuffer; } + @C.Encoding private static int getEncodingForMimeType(String mimeType) { switch (mimeType) { case MimeTypes.AUDIO_AC3: @@ -1038,7 +1041,7 @@ public final class AudioTrack { } } - private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) { + private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) { return DtsUtil.parseDtsAudioSampleCount(buffer); } else if (encoding == C.ENCODING_AC3) { diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java b/library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java index cb408714f4..773959fbfc 100644 --- a/library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; */ public abstract class Buffer { + @C.BufferFlags private int flags; /** @@ -58,7 +59,7 @@ public abstract class Buffer { * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*} * constants. */ - public final void setFlags(int flags) { + public final void setFlags(@C.BufferFlags int flags) { this.flags = flags; } @@ -68,7 +69,7 @@ public abstract class Buffer { * @param flag The flag to add to this buffer's flags, which should be one of the * {@code C.BUFFER_FLAG_*} constants. */ - public final void addFlag(int flag) { + public final void addFlag(@C.BufferFlags int flag) { flags |= flag; } @@ -77,7 +78,7 @@ public abstract class Buffer { * * @param flag The flag to remove. */ - public final void clearFlag(int flag) { + public final void clearFlag(@C.BufferFlags int flag) { flags &= ~flag; } @@ -87,7 +88,7 @@ public abstract class Buffer { * @param flag The flag to check. * @return Whether the flag is set. */ - protected final boolean getFlag(int flag) { + protected final boolean getFlag(@C.BufferFlags int flag) { return (flags & flag) == flag; } diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java b/library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java index bde46738fe..866e421acc 100644 --- a/library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java +++ b/library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.decoder; import android.annotation.TargetApi; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; /** @@ -34,6 +35,7 @@ public final class CryptoInfo { /** * @see android.media.MediaCodec.CryptoInfo#mode */ + @C.CryptoMode public int mode; /** * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData @@ -58,7 +60,7 @@ public final class CryptoInfo { * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) */ public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, - byte[] key, byte[] iv, int mode) { + byte[] key, byte[] iv, @C.CryptoMode int mode) { this.numSubSamples = numSubSamples; this.numBytesOfClearData = numBytesOfClearData; this.numBytesOfEncryptedData = numBytesOfEncryptedData; diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index f34861a0e0..b76f3e8d0c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -15,7 +15,10 @@ */ package com.google.android.exoplayer2.decoder; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; /** @@ -23,16 +26,21 @@ import java.nio.ByteBuffer; */ public class DecoderInputBuffer extends Buffer { + /** + * The buffer replacement mode, which may disable replacement. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, + BUFFER_REPLACEMENT_MODE_DIRECT}) + public @interface BufferReplacementMode {} /** * Disallows buffer replacement. */ public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; - /** * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. */ public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; - /** * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. */ @@ -53,6 +61,7 @@ public class DecoderInputBuffer extends Buffer { */ public long timeUs; + @BufferReplacementMode private final int bufferReplacementMode; /** @@ -60,7 +69,7 @@ public class DecoderInputBuffer extends Buffer { * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. */ - public DecoderInputBuffer(int bufferReplacementMode) { + public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) { this.cryptoInfo = new CryptoInfo(); this.bufferReplacementMode = bufferReplacementMode; } diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index 8f0ff961a9..6f84395072 100644 --- a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -16,6 +16,9 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A DRM session. @@ -23,6 +26,12 @@ import android.annotation.TargetApi; @TargetApi(16) public interface DrmSession { + /** + * The state of the DRM session. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) + @interface State {} /** * The session has encountered an error. {@link #getError()} can be used to retrieve the cause. */ @@ -50,6 +59,7 @@ public interface DrmSession { * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. */ + @State int getState(); /** diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java index 375ffc2733..8e63fbfaae 100644 --- a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java @@ -37,6 +37,6 @@ public interface DrmSessionManager { /** * Releases a {@link DrmSession}. */ - void releaseSession(DrmSession drmSession); + void releaseSession(DrmSession drmSession); } diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/StreamingDrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer2/drm/StreamingDrmSessionManager.java index 8428b0cdf9..f3c6595736 100644 --- a/library/src/main/java/com/google/android/exoplayer2/drm/StreamingDrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer2/drm/StreamingDrmSessionManager.java @@ -40,7 +40,7 @@ import java.util.UUID; * A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}. */ @TargetApi(18) -public class StreamingDrmSessionManager implements DrmSessionManager, +public class StreamingDrmSessionManager implements DrmSessionManager, DrmSession { /** @@ -87,6 +87,7 @@ public class StreamingDrmSessionManager implements Drm private int openCount; private boolean provisioningInProgress; + @DrmSession.State private int state; private T mediaCrypto; private Exception lastException; @@ -267,7 +268,7 @@ public class StreamingDrmSessionManager implements Drm } @Override - public void releaseSession(DrmSession session) { + public void releaseSession(DrmSession session) { if (--openCount != 0) { return; } @@ -291,6 +292,7 @@ public class StreamingDrmSessionManager implements Drm // DrmSession implementation. @Override + @DrmSession.State public final int getState() { return state; } diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java index d1b70279ce..505750efaa 100644 --- a/library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java +++ b/library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java @@ -15,11 +15,21 @@ */ package com.google.android.exoplayer2.drm; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Thrown when the requested DRM scheme is not supported. */ public final class UnsupportedDrmException extends Exception { + /** + * The reason for the exception. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) + public @interface Reason {} /** * The requested DRM scheme is unsupported by the device. */ @@ -33,12 +43,13 @@ public final class UnsupportedDrmException extends Exception { /** * Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. */ + @Reason public final int reason; /** * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. */ - public UnsupportedDrmException(int reason) { + public UnsupportedDrmException(@Reason int reason) { this.reason = reason; } @@ -46,7 +57,7 @@ public final class UnsupportedDrmException extends Exception { * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. * @param cause The cause of this exception. */ - public UnsupportedDrmException(int reason, Exception cause) { + public UnsupportedDrmException(@Reason int reason, Exception cause) { super(cause); this.reason = reason; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java index 6a20b08113..bca5ecf3bd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java @@ -125,7 +125,6 @@ public final class DefaultExtractorInput implements ExtractorInput { throws IOException, InterruptedException { ensureSpaceForPeek(length); int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); - peekBufferLength += length - bytesPeeked; while (bytesPeeked < length) { bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput); @@ -134,6 +133,7 @@ public final class DefaultExtractorInput implements ExtractorInput { } } peekBufferPosition += length; + peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); return true; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index 847d429090..cb9e41aa62 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -298,6 +298,7 @@ public final class DefaultTrackOutput implements TrackOutput { long offset = extrasHolder.offset; // Read the signal byte. + scratch.reset(1); readData(offset, scratch.data, 1); offset++; byte signalByte = scratch.data[0]; @@ -314,9 +315,9 @@ public final class DefaultTrackOutput implements TrackOutput { // Read the subsample count, if present. int subsampleCount; if (subsampleEncryption) { + scratch.reset(2); readData(offset, scratch.data, 2); offset += 2; - scratch.setPosition(0); subsampleCount = scratch.readUnsignedShort(); } else { subsampleCount = 1; @@ -333,7 +334,7 @@ public final class DefaultTrackOutput implements TrackOutput { } if (subsampleEncryption) { int subsampleDataLength = 6 * subsampleCount; - ensureCapacity(scratch, subsampleDataLength); + scratch.reset(subsampleDataLength); readData(offset, scratch.data, subsampleDataLength); offset += subsampleDataLength; scratch.setPosition(0); @@ -412,15 +413,6 @@ public final class DefaultTrackOutput implements TrackOutput { } } - /** - * Ensure that the passed {@link ParsableByteArray} is of at least the specified limit. - */ - private static void ensureCapacity(ParsableByteArray byteArray, int limit) { - if (byteArray.limit() < limit) { - byteArray.reset(new byte[limit], limit); - } - } - // Called by the loading thread. /** @@ -504,7 +496,8 @@ public final class DefaultTrackOutput implements TrackOutput { } @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { if (!startWriteOperation()) { infoQueue.commitSampleTimestamp(timeUs); return; @@ -844,8 +837,8 @@ public final class DefaultTrackOutput implements TrackOutput { } } - public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, - byte[] encryptionKey) { + public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, + int size, byte[] encryptionKey) { Assertions.checkState(!upstreamFormatRequired); commitSampleTimestamp(timeUs); timesUs[relativeWriteIndex] = timeUs; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java index e317dd589c..61f97887be 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java @@ -50,7 +50,8 @@ public final class DummyTrackOutput implements TrackOutput { } @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { // Do nothing. } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java index 0ce807ebb1..c4dee4b6a7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java @@ -72,6 +72,7 @@ public interface TrackOutput { * whose metadata is being passed. * @param encryptionKey The encryption key associated with the sample. May be null. */ - void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey); + void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index c658ab3bcd..bc2d891dab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -163,6 +163,9 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_CUE_TRACK_POSITIONS = 0xB7; private static final int ID_CUE_CLUSTER_POSITION = 0xF1; private static final int ID_LANGUAGE = 0x22B59C; + private static final int ID_PROJECTION = 0x7670; + private static final int ID_PROJECTION_PRIVATE = 0x7672; + private static final int ID_STEREO_MODE = 0x53B8; private static final int LACING_NONE = 0; private static final int LACING_XIPH = 1; @@ -264,6 +267,7 @@ public final class MatroskaExtractor implements Extractor { private int[] blockLacingSampleSizes; private int blockTrackNumber; private int blockTrackNumberLength; + @C.BufferFlags private int blockFlags; // Sample reading state. @@ -361,6 +365,7 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_POINT: case ID_CUE_TRACK_POSITIONS: case ID_BLOCK_GROUP: + case ID_PROJECTION: return EbmlReader.TYPE_MASTER; case ID_EBML_READ_VERSION: case ID_DOC_TYPE_READ_VERSION: @@ -390,6 +395,7 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_TIME: case ID_CUE_CLUSTER_POSITION: case ID_REFERENCE_BLOCK: + case ID_STEREO_MODE: return EbmlReader.TYPE_UNSIGNED_INT; case ID_DOC_TYPE: case ID_CODEC_ID: @@ -401,6 +407,7 @@ public final class MatroskaExtractor implements Extractor { case ID_SIMPLE_BLOCK: case ID_BLOCK: case ID_CODEC_PRIVATE: + case ID_PROJECTION_PRIVATE: return EbmlReader.TYPE_BINARY; case ID_DURATION: case ID_SAMPLING_FREQUENCY: @@ -655,6 +662,22 @@ public final class MatroskaExtractor implements Extractor { case ID_BLOCK_DURATION: blockDurationUs = scaleTimecodeToUs(value); return; + case ID_STEREO_MODE: + int layout = (int) value; + switch (layout) { + case 0: + currentTrack.stereoMode = C.STEREO_MODE_MONO; + break; + case 1: + currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT; + break; + case 3: + currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM; + break; + default: + break; + } + return; default: return; } @@ -705,6 +728,10 @@ public final class MatroskaExtractor implements Extractor { currentTrack.codecPrivate = new byte[contentSize]; input.readFully(currentTrack.codecPrivate, 0, contentSize); return; + case ID_PROJECTION_PRIVATE: + currentTrack.projectionData = new byte[contentSize]; + input.readFully(currentTrack.projectionData, 0, contentSize); + return; case ID_CONTENT_COMPRESSION_SETTINGS: // This extractor only supports header stripping, so the payload is the stripped bytes. currentTrack.sampleStrippedBytes = new byte[contentSize]; @@ -950,13 +977,9 @@ public final class MatroskaExtractor implements Extractor { samplePartitionCountRead = true; } int samplePartitionDataSize = samplePartitionCount * 4; - if (scratch.limit() < samplePartitionDataSize) { - scratch.reset(new byte[samplePartitionDataSize], samplePartitionDataSize); - } + scratch.reset(samplePartitionDataSize); input.readFully(scratch.data, 0, samplePartitionDataSize); sampleBytesRead += samplePartitionDataSize; - scratch.setPosition(0); - scratch.setLimit(samplePartitionDataSize); short subsampleCount = (short) (1 + (samplePartitionCount / 2)); int subsampleDataSize = 2 + 6 * subsampleCount; if (encryptionSubsampleDataBuffer == null @@ -1295,6 +1318,9 @@ public final class MatroskaExtractor implements Extractor { public int displayWidth = Format.NO_VALUE; public int displayHeight = Format.NO_VALUE; public int displayUnit = DISPLAY_UNIT_PIXELS; + public byte[] projectionData = null; + @C.StereoMode + public int stereoMode = Format.NO_VALUE; // Audio elements. Initially set to their default values. public int channelCount = 1; @@ -1318,7 +1344,7 @@ public final class MatroskaExtractor implements Extractor { public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { String mimeType; int maxInputSize = Format.NO_VALUE; - int pcmEncoding = Format.NO_VALUE; + @C.PcmEncoding int pcmEncoding = Format.NO_VALUE; List initializationData = null; switch (codecId) { case CODEC_ID_VP8: @@ -1433,9 +1459,9 @@ public final class MatroskaExtractor implements Extractor { } Format format; - int selectionFlags = 0; - selectionFlags |= flagDefault ? Format.SELECTION_FLAG_DEFAULT : 0; - selectionFlags |= flagForced ? Format.SELECTION_FLAG_FORCED : 0; + @C.SelectionFlags int selectionFlags = 0; + selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0; + selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0; // TODO: Consider reading the name elements of the tracks and, if present, incorporating them // into the trackId passed when creating the formats. if (MimeTypes.isAudio(mimeType)) { @@ -1453,7 +1479,7 @@ public final class MatroskaExtractor implements Extractor { } format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, drmInitData); + Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, selectionFlags, language, drmInitData); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index fdd037cde3..ab501af1cb 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -131,8 +131,12 @@ public final class Mp3Extractor implements Extractor { @Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { - if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) { - return RESULT_END_OF_INPUT; + if (synchronizedHeaderData == 0) { + try { + synchronize(input, false); + } catch (EOFException e) { + return RESULT_END_OF_INPUT; + } } if (seeker == null) { seeker = setupSeeker(input); @@ -147,9 +151,20 @@ public final class Mp3Extractor implements Extractor { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { if (sampleBytesRemaining == 0) { - if (!maybeResynchronize(extractorInput)) { + extractorInput.resetPeekPosition(); + if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { return RESULT_END_OF_INPUT; } + scratch.setPosition(0); + int sampleHeaderData = scratch.readInt(); + if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK) + || MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { + // We have lost synchronization, so attempt to resynchronize starting at the next byte. + extractorInput.skipFully(1); + synchronizedHeaderData = 0; + return RESULT_CONTINUE; + } + MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); if (basisTimeUs == C.TIME_UNSET) { basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { @@ -175,49 +190,13 @@ public final class Mp3Extractor implements Extractor { return RESULT_CONTINUE; } - /** - * Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary. - */ - private boolean maybeResynchronize(ExtractorInput extractorInput) - throws IOException, InterruptedException { - extractorInput.resetPeekPosition(); - if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { - return false; - } - - scratch.setPosition(0); - int sampleHeaderData = scratch.readInt(); - if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) { - int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData); - if (frameSize != C.LENGTH_UNSET) { - MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); - return true; - } - } - - synchronizedHeaderData = 0; - extractorInput.skipFully(1); - return synchronizeCatchingEndOfInput(extractorInput); - } - - private boolean synchronizeCatchingEndOfInput(ExtractorInput input) - throws IOException, InterruptedException { - // An EOFException will be raised if any peek operation was partially satisfied. If a seek - // operation resulted in reading from within the last frame, we may try to peek past the end of - // the file in a partially-satisfied read operation, so we need to catch the exception. - try { - return synchronize(input, false); - } catch (EOFException e) { - return false; - } - } - private boolean synchronize(ExtractorInput input, boolean sniffing) throws IOException, InterruptedException { - int searched = 0; int validFrameCount = 0; int candidateSynchronizedHeaderData = 0; int peekedId3Bytes = 0; + int searchedBytes = 0; + int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; input.resetPeekPosition(); if (input.getPosition() == 0) { Id3Util.parseId3(input, gaplessInfoHolder); @@ -227,14 +206,9 @@ public final class Mp3Extractor implements Extractor { } } while (true) { - if (sniffing && searched == MAX_SNIFF_BYTES) { - return false; - } - if (!sniffing && searched == MAX_SYNC_BYTES) { - throw new ParserException("Searched too many bytes."); - } - if (!input.peekFully(scratch.data, 0, 4, true)) { - return false; + if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) { + // We reached the end of the stream but found at least one valid frame. + break; } scratch.setPosition(0); int headerData = scratch.readInt(); @@ -242,18 +216,23 @@ public final class Mp3Extractor implements Extractor { if ((candidateSynchronizedHeaderData != 0 && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { - // The header is invalid or doesn't match the candidate header. Try the next byte offset. + // The header doesn't match the candidate header or is invalid. Try the next byte offset. + if (searchedBytes++ == searchLimitBytes) { + if (!sniffing) { + throw new ParserException("Searched too many bytes."); + } + return false; + } validFrameCount = 0; candidateSynchronizedHeaderData = 0; - searched++; if (sniffing) { input.resetPeekPosition(); - input.advancePeekPosition(peekedId3Bytes + searched); + input.advancePeekPosition(peekedId3Bytes + searchedBytes); } else { input.skipFully(1); } } else { - // The header is valid and matches the candidate header. + // The header matches the candidate header and/or is valid. validFrameCount++; if (validFrameCount == 1) { MpegAudioHeader.populateHeader(headerData, synchronizedHeader); @@ -266,7 +245,7 @@ public final class Mp3Extractor implements Extractor { } // Prepare to read the synchronized frame. if (sniffing) { - input.skipFully(peekedId3Bytes + searched); + input.skipFully(peekedId3Bytes + searchedBytes); } else { input.resetPeekPosition(); } @@ -293,14 +272,17 @@ public final class Mp3Extractor implements Extractor { long position = input.getPosition(); long length = input.getLength(); + int headerData = 0; + Seeker seeker = null; // Check if there is a Xing header. int xingBase = (synchronizedHeader.version & 1) != 0 ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 - frame.setPosition(xingBase); - int headerData = frame.readInt(); - Seeker seeker = null; + if (frame.limit() >= xingBase + 4) { + frame.setPosition(xingBase); + headerData = frame.readInt(); + } if (headerData == XING_HEADER || headerData == INFO_HEADER) { seeker = XingSeeker.create(synchronizedHeader, frame, position, length); if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { @@ -312,7 +294,7 @@ public final class Mp3Extractor implements Extractor { gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); } input.skipFully(synchronizedHeader.frameSize); - } else { + } else if (frame.limit() >= 40) { // Check if there is a VBRI header. frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. headerData = frame.readInt(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 11a25fe419..0ca0f216c6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -38,6 +39,8 @@ import java.util.List; */ /* package */ final class AtomParsers { + private static final String TAG = "AtomParsers"; + private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); private static final int TYPE_soun = Util.getIntegerCodeForString("soun"); private static final int TYPE_text = Util.getIntegerCodeForString("text"); @@ -248,11 +251,16 @@ import java.util.List; remainingTimestampOffsetChanges--; } - // Check all the expected samples have been seen. - Assertions.checkArgument(remainingSynchronizationSamples == 0); - Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0); - Assertions.checkArgument(remainingSamplesInChunk == 0); - Assertions.checkArgument(remainingTimestampDeltaChanges == 0); + // If the stbl's child boxes are not consistent the container is malformed, but the stream may + // still be playable. + if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0 + || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) { + Log.w(TAG, "Inconsistent stbl box for track " + track.id + + ": remainingSynchronizationSamples " + remainingSynchronizationSamples + + ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta + + ", remainingSamplesInChunk " + remainingSamplesInChunk + + ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges); + } } else { long[] chunkOffsetsBytes = new long[chunkIterator.length]; int[] chunkSampleCounts = new int[chunkIterator.length]; @@ -636,7 +644,7 @@ import java.util.List; 0 /* subsample timing is absolute */); } else if (childAtomType == Atom.TYPE_c608) { out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, 0, language, drmInitData); + MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData); out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; } stsd.setPosition(childStartPosition + childAtomSize); @@ -665,6 +673,7 @@ import java.util.List; List initializationData = null; String mimeType = null; byte[] projectionData = null; + @C.StereoMode int stereoMode = Format.NO_VALUE; while (childPosition - position < size) { parent.setPosition(childPosition); @@ -889,7 +898,7 @@ import java.util.List; if (out.format == null && mimeType != null) { // TODO: Determine the correct PCM encoding. - int pcmEncoding = + @C.PcmEncoding int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE; out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, @@ -1169,6 +1178,7 @@ import java.util.List; public Format format; public int nalUnitLengthFieldLength; + @Track.Transformation public int requiredSampleTransformation; public StsdData(int numberOfEntries) { diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 103acdd6f9..71f9231a31 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import android.support.annotation.IntDef; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -64,6 +67,13 @@ public final class FragmentedMp4Extractor implements Extractor { private static final String TAG = "FragmentedMp4Extractor"; private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, + FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED}) + public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. * The workaround overrides the sync frame flags in the stream, forcing them to false except for @@ -72,12 +82,10 @@ public final class FragmentedMp4Extractor implements Extractor { * This flag does nothing if the stream is not a video stream. */ public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; - /** * Flag to ignore any tfdt boxes in the stream. */ public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2; - /** * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4 * container. @@ -95,6 +103,7 @@ public final class FragmentedMp4Extractor implements Extractor { private static final int STATE_READING_SAMPLE_CONTINUE = 4; // Workarounds. + @Flags private final int flags; private final Track sideloadedTrack; @@ -135,18 +144,18 @@ public final class FragmentedMp4Extractor implements Extractor { } /** - * @param flags Flags to allow parsing of faulty streams. + * @param flags Flags that control the extractor's behavior. */ - public FragmentedMp4Extractor(int flags) { + public FragmentedMp4Extractor(@Flags int flags) { this(flags, null); } /** - * @param flags Flags to allow parsing of faulty streams. + * @param flags Flags that control the extractor's behavior. * @param sideloadedTrack Sideloaded track information, in the case that the extractor * will not receive a moov box in the input data. */ - public FragmentedMp4Extractor(int flags, Track sideloadedTrack) { + public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) { this.sideloadedTrack = sideloadedTrack; this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); @@ -422,7 +431,7 @@ public final class FragmentedMp4Extractor implements Extractor { } private static void parseMoof(ContainerAtom moof, SparseArray trackBundleArray, - int flags, byte[] extendedTypeScratch) throws ParserException { + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); for (int i = 0; i < moofContainerChildrenSize; i++) { Atom.ContainerAtom child = moof.containerChildren.get(i); @@ -437,7 +446,7 @@ public final class FragmentedMp4Extractor implements Extractor { * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray, - int flags, byte[] extendedTypeScratch) throws ParserException { + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); if (trackBundle == null) { @@ -488,7 +497,7 @@ public final class FragmentedMp4Extractor implements Extractor { } private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, - int flags) { + @Flags int flags) { int trunCount = 0; int totalSampleCount = 0; List leafChildren = traf.leafChildren; @@ -643,8 +652,8 @@ public final class FragmentedMp4Extractor implements Extractor { * @param trun The trun atom to decode. * @return The starting position of samples for the next run. */ - private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags, - ParsableByteArray trun, int trackRunStart) { + private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, + @Flags int flags, ParsableByteArray trun, int trackRunStart) { trun.setPosition(Atom.HEADER_SIZE); int fullAtom = trun.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); @@ -994,7 +1003,7 @@ public final class FragmentedMp4Extractor implements Extractor { } long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; - int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) + @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; byte[] encryptionKey = null; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java index 842b648ec2..44d5824945 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java @@ -100,13 +100,14 @@ import java.io.IOException; while (bytesSearched < bytesToSearch) { // Read an atom header. int headerSize = Atom.HEADER_SIZE; + buffer.reset(headerSize); input.peekFully(buffer.data, 0, headerSize); - buffer.setPosition(0); long atomSize = buffer.readUnsignedInt(); int atomType = buffer.readInt(); if (atomSize == Atom.LONG_SIZE_PREFIX) { headerSize = Atom.LONG_HEADER_SIZE; input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); + buffer.setLimit(Atom.LONG_HEADER_SIZE); atomSize = buffer.readUnsignedLongToLong(); } @@ -139,9 +140,7 @@ import java.io.IOException; if (atomDataSize < 8) { return false; } - if (buffer.capacity() < atomDataSize) { - buffer.reset(new byte[atomDataSize], atomDataSize); - } + buffer.reset(atomDataSize); input.peekFully(buffer.data, 0, atomDataSize); int brandsCount = atomDataSize / 4; for (int i = 0; i < brandsCount; i++) { diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 5d48529794..c723704d37 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -15,14 +15,23 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Encapsulates information describing an MP4 track. */ public final class Track { + /** + * The transformation to apply to samples in the track, if any. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) + public @interface Transformation {} /** * A no-op sample transformation. */ @@ -66,6 +75,7 @@ public final class Track { * One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each * sample. */ + @Transformation public final int sampleTransformation; /** @@ -90,7 +100,7 @@ public final class Track { public final int nalUnitLengthFieldLength; public Track(int id, int type, long timescale, long movieTimescale, long durationUs, - Format format, int sampleTransformation, + Format format, @Transformation int sampleTransformation, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) { this.id = id; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java index 54e3fcd957..cf479eaf3e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java @@ -49,7 +49,8 @@ import com.google.android.exoplayer2.util.Util; */ public final int[] flags; - TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) { + public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, + int[] flags) { Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java index 8a63c5557b..ea9458a657 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** - * Extracts EIA-608 data from a RawCC file + * Extracts CEA data from a RawCC file. */ public final class RawCcExtractor implements Extractor { @@ -68,7 +68,7 @@ public final class RawCcExtractor implements Extractor { trackOutput = extractorOutput.track(0); extractorOutput.endTracks(); - trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, + trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); } @@ -154,13 +154,8 @@ public final class RawCcExtractor implements Extractor { dataScratch.reset(); input.readFully(dataScratch.data, 0, 3); - // only accept EIA-608 packets which have validity (6th bit) == 1 and - // type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100 - int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07; - if (ccValidityAndType == 0x04) { - trackOutput.sampleData(dataScratch, 2); - sampleBytesWritten += 2; - } + trackOutput.sampleData(dataScratch, 3); + sampleBytesWritten += 3; } if (sampleBytesWritten > 0) { diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java new file mode 100644 index 0000000000..d5e3b78cfd --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.ts; + +import android.support.annotation.IntDef; +import android.util.SparseBooleanArray; +import com.google.android.exoplayer2.extractor.DummyTrackOutput; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Default implementation for {@link ElementaryStreamReader.Factory}. + */ +public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory { + + /** + * Flags controlling what workarounds are enabled for elementary stream readers. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM, + WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE}) + public @interface WorkaroundFlags { + } + public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1; + public static final int WORKAROUND_IGNORE_AAC_STREAM = 2; + public static final int WORKAROUND_IGNORE_H264_STREAM = 4; + public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8; + public static final int WORKAROUND_MAP_BY_TYPE = 16; + + private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1. + + private final SparseBooleanArray trackIds; + @WorkaroundFlags + private final int workaroundFlags; + private Id3Reader id3Reader; + private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID; + + public DefaultStreamReaderFactory() { + this(0); + } + + public DefaultStreamReaderFactory(int workaroundFlags) { + trackIds = new SparseBooleanArray(); + this.workaroundFlags = workaroundFlags; + } + + @Override + public ElementaryStreamReader onPmtEntry(int pid, int streamType, + ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) { + + if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) { + // Setup an ID3 track regardless of whether there's a corresponding entry, in case one + // appears intermittently during playback. See b/20261500. + id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3)); + } + int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid; + if (trackIds.get(trackId)) { + return null; + } + trackIds.put(trackId, true); + switch (streamType) { + case TsExtractor.TS_STREAM_TYPE_MPA: + case TsExtractor.TS_STREAM_TYPE_MPA_LSF: + return new MpegAudioReader(output.track(trackId), esInfo.language); + case TsExtractor.TS_STREAM_TYPE_AAC: + return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null + : new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language); + case TsExtractor.TS_STREAM_TYPE_AC3: + case TsExtractor.TS_STREAM_TYPE_E_AC3: + return new Ac3Reader(output.track(trackId), esInfo.language); + case TsExtractor.TS_STREAM_TYPE_DTS: + case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: + return new DtsReader(output.track(trackId), esInfo.language); + case TsExtractor.TS_STREAM_TYPE_H262: + return new H262Reader(output.track(trackId)); + case TsExtractor.TS_STREAM_TYPE_H264: + return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 + ? null : new H264Reader(output.track(trackId), + new SeiReader(output.track(nextEmbeddedTrackId++)), + (workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0, + (workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0); + case TsExtractor.TS_STREAM_TYPE_H265: + return new H265Reader(output.track(trackId), + new SeiReader(output.track(nextEmbeddedTrackId++))); + case TsExtractor.TS_STREAM_TYPE_ID3: + if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) { + return id3Reader; + } else { + return new Id3Reader(output.track(nextEmbeddedTrackId++)); + } + default: + return null; + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java index 13029ca04e..7a220c98b3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -15,13 +15,60 @@ */ package com.google.android.exoplayer2.extractor.ts; +import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Extracts individual samples from an elementary media stream, preserving original order. */ -/* package */ abstract class ElementaryStreamReader { +public abstract class ElementaryStreamReader { + + /** + * Factory of {@link ElementaryStreamReader} instances. + */ + public interface Factory { + + /** + * Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the + * stream type is not supported or if the stream already has a reader assigned to it. + * + * @param pid The pid for the PMT entry. + * @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining + * the type of the stream. + * @param esInfo The descriptor information linked to the elementary stream. + * @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the + * created readers. + * @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided + * pid. {@code null} if the stream is not supported or if it should be ignored. + */ + ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo, + ExtractorOutput output); + + } + + /** + * Holds descriptor information associated with an elementary stream. + */ + public static final class EsInfo { + + public final int streamType; + public String language; + public byte[] descriptorBytes; + + /** + * @param streamType The type of the stream as defined by the + * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. + * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param descriptorBytes The descriptor bytes associated to the stream. + */ + public EsInfo(int streamType, String language, byte[] descriptorBytes) { + this.streamType = streamType; + this.language = language; + this.descriptorBytes = descriptorBytes; + } + + } protected final TrackOutput output; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index a898ff598c..cdbd8e391d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -128,7 +128,7 @@ import java.util.Collections; if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { int bytesWrittenPastStartCode = limit - startCodeOffset; if (foundFirstFrameInGroup) { - int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + @C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); isKeyframe = false; @@ -136,7 +136,7 @@ import java.util.Collections; if (startCodeValue == START_GROUP) { foundFirstFrameInGroup = false; isKeyframe = true; - } else /* startCode == START_PICTURE */ { + } else /* startCodeValue == START_PICTURE */ { frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); framePosition = totalBytesWritten - bytesWrittenPastStartCode; pesPtsUsAvailable = false; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index 8bb72f6e79..ce7b7e6383 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -57,7 +57,7 @@ import java.util.List; /** * @param output A {@link TrackOutput} to which H.264 samples should be written. - * @param seiReader A reader for EIA-608 samples in SEI NAL units. + * @param seiReader A reader for CEA-608 samples in SEI NAL units. * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as * synchronization samples (key-frames). * @param detectAccessUnits Whether to split the input stream into access units (samples) based on @@ -420,7 +420,7 @@ import java.util.List; } private void outputSample(int offset) { - int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; int size = (int) (nalUnitStartPosition - samplePosition); output.sampleMetadata(sampleTimeUs, flags, size, offset, null); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index d6c431edec..c8828cefa6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -64,7 +64,7 @@ import java.util.Collections; /** * @param output A {@link TrackOutput} to which H.265 samples should be written. - * @param seiReader A reader for EIA-608 samples in SEI NAL units. + * @param seiReader A reader for CEA-608 samples in SEI NAL units. */ public H265Reader(TrackOutput output, SeiReader seiReader) { super(output); @@ -471,7 +471,7 @@ import java.util.Collections; } private void outputSample(int offset) { - int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; int size = (int) (nalUnitStartPosition - samplePosition); output.sampleMetadata(sampleTimeUs, flags, size, offset, null); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index ff86be4165..d69a2a1e96 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -153,8 +153,7 @@ public final class PsExtractor implements Extractor { input.peekFully(psPacketBuffer.data, 0, 10); // We only care about the pack_stuffing_length in here, skip the first 77 bits. - psPacketBuffer.setPosition(0); - psPacketBuffer.skipBytes(9); + psPacketBuffer.setPosition(9); // Last 3 bits is the length. int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07; @@ -209,7 +208,7 @@ public final class PsExtractor implements Extractor { } } - // The next 2 bytes are the length, once we have that we can consume the complete packet. + // The next 2 bytes are the length. Once we have that we can consume the complete packet. input.peekFully(psPacketBuffer.data, 0, 2); psPacketBuffer.setPosition(0); int payloadLength = psPacketBuffer.readUnsignedShort(); @@ -219,14 +218,10 @@ public final class PsExtractor implements Extractor { // Just skip this data. input.skipFully(pesLength); } else { - if (psPacketBuffer.capacity() < pesLength) { - // Reallocate for this and future packets. - psPacketBuffer.reset(new byte[pesLength], pesLength); - } + psPacketBuffer.reset(pesLength); // Read the whole packet and the header for consumption. input.readFully(psPacketBuffer.data, 0, pesLength); psPacketBuffer.setPosition(6); - psPacketBuffer.setLimit(pesLength); payloadReader.consume(psPacketBuffer); psPacketBuffer.setLimit(psPacketBuffer.capacity()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java index a921e4c38f..4971c0c2b1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java @@ -18,12 +18,12 @@ package com.google.android.exoplayer2.extractor.ts; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.text.eia608.Eia608Decoder; +import com.google.android.exoplayer2.text.cea.Cea608Decoder; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; /** - * Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}. + * Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}. */ /* package */ final class SeiReader { @@ -31,7 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; public SeiReader(TrackOutput output) { this.output = output; - output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, null, + output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); } @@ -51,7 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; payloadSize += b; } while (b == 0xFF); // Process the payload. - if (Eia608Decoder.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { + if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { // Ignore country_code (1) + provider_code (2) + user_identifier (4) // + user_data_type_code (1). seiBuffer.skipBytes(8); @@ -60,13 +60,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray; seiBuffer.skipBytes(1); int sampleBytes = 0; for (int i = 0; i < ccCount; i++) { - int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07; - // Check that validity == 1 and type == 0. + int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07; + // Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1). if (ccValidityAndType != 0x04) { - seiBuffer.skipBytes(2); + seiBuffer.skipBytes(3); } else { - sampleBytes += 2; - output.sampleData(seiBuffer, 2); + sampleBytes += 3; + output.sampleData(seiBuffer, 3); } } output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 5c9e8cf673..897603bc13 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -17,10 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; import android.util.Log; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -32,6 +30,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.util.Arrays; /** * Facilitates the extraction of data from the MPEG-2 TS container format. @@ -50,30 +49,24 @@ public final class TsExtractor implements Extractor { }; - public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1; - public static final int WORKAROUND_IGNORE_AAC_STREAM = 2; - public static final int WORKAROUND_IGNORE_H264_STREAM = 4; - public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8; - public static final int WORKAROUND_MAP_BY_TYPE = 16; - private static final String TAG = "TsExtractor"; private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_PAT_PID = 0; - private static final int TS_STREAM_TYPE_MPA = 0x03; - private static final int TS_STREAM_TYPE_MPA_LSF = 0x04; - private static final int TS_STREAM_TYPE_AAC = 0x0F; - private static final int TS_STREAM_TYPE_AC3 = 0x81; - private static final int TS_STREAM_TYPE_DTS = 0x8A; - private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; - private static final int TS_STREAM_TYPE_E_AC3 = 0x87; - private static final int TS_STREAM_TYPE_H262 = 0x02; - private static final int TS_STREAM_TYPE_H264 = 0x1B; - private static final int TS_STREAM_TYPE_H265 = 0x24; - private static final int TS_STREAM_TYPE_ID3 = 0x15; - private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1 + public static final int TS_STREAM_TYPE_MPA = 0x03; + public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; + public static final int TS_STREAM_TYPE_AAC = 0x0F; + public static final int TS_STREAM_TYPE_AC3 = 0x81; + public static final int TS_STREAM_TYPE_DTS = 0x8A; + public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; + public static final int TS_STREAM_TYPE_E_AC3 = 0x87; + public static final int TS_STREAM_TYPE_H262 = 0x02; + public static final int TS_STREAM_TYPE_H264 = 0x1B; + public static final int TS_STREAM_TYPE_H265 = 0x24; + public static final int TS_STREAM_TYPE_ID3 = 0x15; + private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); @@ -83,35 +76,38 @@ public final class TsExtractor implements Extractor { private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT; private final TimestampAdjuster timestampAdjuster; - private final int workaroundFlags; private final ParsableByteArray tsPacketBuffer; private final ParsableBitArray tsScratch; private final SparseIntArray continuityCounters; + private final ElementaryStreamReader.Factory streamReaderFactory; /* package */ final SparseArray tsPayloadReaders; // Indexed by pid - /* package */ final SparseBooleanArray trackIds; // Accessed only by the loading thread. private ExtractorOutput output; - private int nextEmbeddedTrackId; - /* package */ Id3Reader id3Reader; public TsExtractor() { this(new TimestampAdjuster(0)); } + /** + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + */ public TsExtractor(TimestampAdjuster timestampAdjuster) { - this(timestampAdjuster, 0); + this(timestampAdjuster, new DefaultStreamReaderFactory()); } - public TsExtractor(TimestampAdjuster timestampAdjuster, int workaroundFlags) { + /** + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param customReaderFactory Factory for injecting a custom set of elementary stream readers. + */ + public TsExtractor(TimestampAdjuster timestampAdjuster, + ElementaryStreamReader.Factory customReaderFactory) { this.timestampAdjuster = timestampAdjuster; - this.workaroundFlags = workaroundFlags; + this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory); tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsScratch = new ParsableBitArray(new byte[3]); tsPayloadReaders = new SparseArray<>(); tsPayloadReaders.put(TS_PAT_PID, new PatReader()); - trackIds = new SparseBooleanArray(); - nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID; continuityCounters = new SparseIntArray(); } @@ -416,12 +412,6 @@ public final class TsExtractor implements Extractor { // Skip the descriptors. sectionData.skipBytes(programInfoLength); - if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) { - // Setup an ID3 track regardless of whether there's a corresponding entry, in case one - // appears intermittently during playback. See b/20261500. - id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3)); - } - int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */ - programInfoLength - 4 /* CRC length */; while (remainingEntriesLength > 0) { @@ -431,63 +421,15 @@ public final class TsExtractor implements Extractor { int elementaryPid = pmtScratch.readBits(13); pmtScratch.skipBits(4); // reserved int esInfoLength = pmtScratch.readBits(12); // ES_info_length. - EsInfo esInfo = readEsInfo(sectionData, esInfoLength); + ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength); if (streamType == 0x06) { streamType = esInfo.streamType; } remainingEntriesLength -= esInfoLength + 5; - int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid; - if (trackIds.get(trackId)) { - continue; - } - ElementaryStreamReader pesPayloadReader; - switch (streamType) { - case TS_STREAM_TYPE_MPA: - pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language); - break; - case TS_STREAM_TYPE_MPA_LSF: - pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.language); - break; - case TS_STREAM_TYPE_AAC: - pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null - : new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language); - break; - case TS_STREAM_TYPE_AC3: - case TS_STREAM_TYPE_E_AC3: - pesPayloadReader = new Ac3Reader(output.track(trackId), esInfo.language); - break; - case TS_STREAM_TYPE_DTS: - case TS_STREAM_TYPE_HDMV_DTS: - pesPayloadReader = new DtsReader(output.track(trackId), esInfo.language); - break; - case TS_STREAM_TYPE_H262: - pesPayloadReader = new H262Reader(output.track(trackId)); - break; - case TS_STREAM_TYPE_H264: - pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null - : new H264Reader(output.track(trackId), - new SeiReader(output.track(nextEmbeddedTrackId++)), - (workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0, - (workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0); - break; - case TS_STREAM_TYPE_H265: - pesPayloadReader = new H265Reader(output.track(trackId), - new SeiReader(output.track(nextEmbeddedTrackId++))); - break; - case TS_STREAM_TYPE_ID3: - if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) { - pesPayloadReader = id3Reader; - } else { - pesPayloadReader = new Id3Reader(output.track(nextEmbeddedTrackId++)); - } - break; - default: - pesPayloadReader = null; - break; - } + ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid, + streamType, esInfo, output); if (pesPayloadReader != null) { - trackIds.put(trackId, true); tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster)); } @@ -497,18 +439,17 @@ public final class TsExtractor implements Extractor { } /** - * Returns the stream info read from the available descriptors, or -1 if no - * descriptors are present. Sets {@code data}'s position to the end of the descriptors. + * Returns the stream info read from the available descriptors. Sets {@code data}'s position to + * the end of the descriptors. * * @param data A buffer with its position set to the start of the first descriptor. * @param length The length of descriptors to read from the current position in {@code data}. - * @return The stream info read from the available descriptors, or -1 if no - * descriptors are present. + * @return The stream info read from the available descriptors. */ - private EsInfo readEsInfo(ParsableByteArray data, int length) { - int descriptorsEndPosition = data.getPosition() + length; + private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) { + int descriptorsStartPosition = data.getPosition(); + int descriptorsEndPosition = descriptorsStartPosition + length; int streamType = -1; - int audioType = -1; String language = null; while (data.getPosition() < descriptorsEndPosition) { int descriptorTag = data.readUnsignedByte(); @@ -531,27 +472,14 @@ public final class TsExtractor implements Extractor { streamType = TS_STREAM_TYPE_DTS; } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { language = new String(data.data, data.getPosition(), 3).trim(); - audioType = data.data[data.getPosition() + 3]; + // Audio type is ignored. } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); } data.setPosition(descriptorsEndPosition); - return new EsInfo(streamType, audioType, language); - } - - private final class EsInfo { - - final int streamType; - final int audioType; - final String language; - - public EsInfo(int streamType, int audioType, String language) { - this.streamType = streamType; - this.audioType = audioType; - this.language = language; - } - + return new ElementaryStreamReader.EsInfo(streamType, language, + Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition)); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java index b5e2268b53..a57060f604 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C; /** Bits per sample for the audio data. */ private final int bitsPerSample; /** The PCM encoding */ + @C.PcmEncoding private final int encoding; /** Offset to the start of sample data. */ @@ -39,7 +40,7 @@ import com.google.android.exoplayer2.C; private long dataSize; public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, - int bitsPerSample, int encoding) { + int bitsPerSample, @C.PcmEncoding int encoding) { this.numChannels = numChannels; this.sampleRateHz = sampleRateHz; this.averageBytesPerSecond = averageBytesPerSecond; @@ -99,6 +100,7 @@ import com.google.android.exoplayer2.C; } /** Returns the PCM encoding. **/ + @C.PcmEncoding public int getEncoding() { return encoding; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index 36fbcf40af..0e99380a1c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -87,7 +87,7 @@ import java.io.IOException; + blockAlignment); } - int encoding = Util.getPcmEncoding(bitsPerSample); + @C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample); if (encoding == C.ENCODING_INVALID) { Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample); return null; diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index dfc37f8472..63a77e2215 100644 --- a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -293,7 +293,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { MediaCrypto mediaCrypto = null; boolean drmSessionRequiresSecureDecoder = false; if (drmSession != null) { - int drmSessionState = drmSession.getState(); + @DrmSession.State int drmSessionState = drmSession.getState(); if (drmSessionState == DrmSession.STATE_ERROR) { throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); } else if (drmSessionState == DrmSession.STATE_OPENED @@ -682,7 +682,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (drmSession == null) { return false; } - int drmSessionState = drmSession.getState(); + @DrmSession.State int drmSessionState = drmSession.getState(); if (drmSessionState == DrmSession.STATE_ERROR) { throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 90af3936a4..3b743c5fda 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Util; import java.io.IOException; @@ -79,12 +78,11 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { int sourceIndex = timeline.getSourceIndexForPeriod(index); int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); - MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, callback, - allocator, positionUs); + MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator, + positionUs); sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); return mediaPeriod; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index a9d4c18db6..e1945c1a79 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -61,12 +61,15 @@ import java.util.Arrays; private final Handler eventHandler; private final ExtractorMediaSource.EventListener eventListener; private final MediaSource.Listener sourceListener; - private final Callback callback; private final Allocator allocator; private final Loader loader; private final ExtractorHolder extractorHolder; private final ConditionVariable loadCondition; + private final Runnable maybeFinishPrepareRunnable; + private final Runnable onContinueLoadingRequestedRunnable; + private final Handler handler; + private Callback callback; private SeekMap seekMap; private boolean tracksBuilt; private boolean prepared; @@ -85,6 +88,7 @@ import java.util.Arrays; private int extractedSamplesCountAtStartOfLoad; private boolean loadingFinished; + private boolean released; /** * @param uri The {@link Uri} of the media stream. @@ -94,30 +98,41 @@ import java.util.Arrays; * @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param sourceListener A listener to notify when the timeline has been loaded. - * @param callback A callback to receive updates from the period. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. */ public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, int minLoadableRetryCount, Handler eventHandler, ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, - Callback callback, Allocator allocator) { + Allocator allocator) { this.uri = uri; this.dataSource = dataSource; this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; this.sourceListener = sourceListener; - this.callback = callback; this.allocator = allocator; loader = new Loader("Loader:ExtractorMediaPeriod"); extractorHolder = new ExtractorHolder(extractors, this); loadCondition = new ConditionVariable(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + onContinueLoadingRequestedRunnable = new Runnable() { + @Override + public void run() { + if (!released) { + callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); + } + } + }; + handler = new Handler(); pendingResetPositionUs = C.TIME_UNSET; sampleQueues = new DefaultTrackOutput[0]; length = C.LENGTH_UNSET; - loadCondition.open(); - startLoading(); } public void release() { @@ -126,11 +141,20 @@ import java.util.Arrays; @Override public void run() { extractorHolder.release(); + for (DefaultTrackOutput sampleQueue : sampleQueues) { + sampleQueue.disable(); + } } }); - for (DefaultTrackOutput sampleQueue : sampleQueues) { - sampleQueue.disable(); - } + handler.removeCallbacksAndMessages(null); + released = true; + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + loadCondition.open(); + startLoading(); } @Override @@ -330,7 +354,7 @@ import java.util.Arrays; return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; } - // ExtractorOutput implementation. + // ExtractorOutput implementation. Called by the loading thread. @Override public TrackOutput track(int id) { @@ -344,26 +368,26 @@ import java.util.Arrays; @Override public void endTracks() { tracksBuilt = true; - maybeFinishPrepare(); + handler.post(maybeFinishPrepareRunnable); } @Override public void seekMap(SeekMap seekMap) { this.seekMap = seekMap; - maybeFinishPrepare(); + handler.post(maybeFinishPrepareRunnable); } - // UpstreamFormatChangedListener implementation + // UpstreamFormatChangedListener implementation. Called by the loading thread. @Override public void onUpstreamFormatChanged(Format format) { - maybeFinishPrepare(); + handler.post(maybeFinishPrepareRunnable); } // Internal methods. private void maybeFinishPrepare() { - if (prepared || seekMap == null || !tracksBuilt) { + if (released || prepared || seekMap == null || !tracksBuilt) { return; } for (DefaultTrackOutput sampleQueue : sampleQueues) { @@ -576,7 +600,7 @@ import java.util.Arrays; if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { position = input.getPosition(); loadCondition.close(); - callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); + handler.post(onContinueLoadingRequestedRunnable); } } } finally { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 936358352e..bf795241bc 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.util.Assertions; @@ -148,12 +147,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { Assertions.checkArgument(index == 0); return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, - this, callback, allocator); + this, allocator); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index e41f5cdc1e..21455ed89d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -19,7 +19,6 @@ import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; @@ -76,9 +75,8 @@ public final class LoopingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { - return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs); + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + return childSource.createPeriod(index % childPeriodCount, allocator, positionUs); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java index 80424d445e..a3c1c88df4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java @@ -32,7 +32,7 @@ public interface MediaPeriod extends SequenceableLoader { /** * Called when preparation completes. *

- * May be called from any thread. After invoking this method, the {@link MediaPeriod} can expect + * Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect * for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be * called with the initial track selection. * @@ -42,6 +42,17 @@ public interface MediaPeriod extends SequenceableLoader { } + /** + * Prepares this media period asynchronously. + *

+ * {@code callback.onPrepared} is called when preparation completes. If preparation fails, + * {@link #maybeThrowPrepareError()} will throw an {@link IOException}. + * + * @param callback Callback to receive updates from this period, including being notified when + * preparation completes. + */ + void prepare(Callback callback); + /** * Throws an error that's preventing the period from becoming prepared. Does nothing if no such * error exists. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 4c0686eec1..709a92cbf5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import java.io.IOException; @@ -54,18 +53,13 @@ public interface MediaSource { /** * Returns a {@link MediaPeriod} corresponding to the period at the specified index. - *

- * {@link Callback#onPrepared(MediaPeriod)} is called when the new period is prepared. If - * preparation fails, {@link MediaPeriod#maybeThrowPrepareError()} will throw an - * {@link IOException} if called on the returned instance. * * @param index The index of the period. - * @param callback A callback to receive updates from the period. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param positionUs The player's current playback position. * @return A new {@link MediaPeriod}. */ - MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, long positionUs); + MediaPeriod createPeriod(int index, Allocator allocator, long positionUs); /** * Releases the period. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 4df6cdfebf..227dc29366 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -28,20 +28,27 @@ import java.util.IdentityHashMap; public final MediaPeriod[] periods; - private final Callback callback; private final IdentityHashMap streamPeriodIndices; + private Callback callback; private int pendingChildPrepareCount; private TrackGroupArray trackGroups; private MediaPeriod[] enabledPeriods; private SequenceableLoader sequenceableLoader; - public MergingMediaPeriod(Callback callback, MediaPeriod... periods) { + public MergingMediaPeriod(MediaPeriod... periods) { this.periods = periods; - this.callback = callback; streamPeriodIndices = new IdentityHashMap<>(); + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; pendingChildPrepareCount = periods.length; + for (MediaPeriod period : periods) { + period.prepare(this); + } } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index b213928516..7d5f78c1cd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -15,11 +15,12 @@ */ package com.google.android.exoplayer2.source; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; @@ -36,11 +37,16 @@ public final class MergingMediaSource implements MediaSource { */ public static final class IllegalMergeException extends IOException { + /** + * The reason the merge failed. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH}) + public @interface Reason {} /** * The merge failed because one of the sources being merged has a dynamic window. */ public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; - /** * The merge failed because the sources have different period counts. */ @@ -50,13 +56,14 @@ public final class MergingMediaSource implements MediaSource { * The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and * {@link #REASON_PERIOD_COUNT_MISMATCH}. */ + @Reason public final int reason; /** * @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and * {@link #REASON_PERIOD_COUNT_MISMATCH}. */ - public IllegalMergeException(int reason) { + public IllegalMergeException(@Reason int reason) { this.reason = reason; } @@ -109,16 +116,12 @@ public final class MergingMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; - // The periods are only referenced after they have all been prepared. - MergingMediaPeriod mergingPeriod = new MergingMediaPeriod(callback, periods); for (int i = 0; i < periods.length; i++) { - periods[i] = mediaSources[i].createPeriod(index, mergingPeriod, allocator, positionUs); - Assertions.checkState(periods[i] != null, "Child source must not return null period"); + periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs); } - return mergingPeriod; + return new MergingMediaPeriod(periods); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java b/library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java index 5ade51376f..9aebcece9e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java @@ -29,7 +29,7 @@ public interface SequenceableLoader { /** * Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method - * to be called when it can continue to load data. + * to be called when it can continue to load data. Called on the playback thread. */ void onContinueLoadingRequested(T source); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index 2e6b22e05c..2888f267ce 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -78,6 +78,11 @@ import java.util.Arrays; loader.release(); } + @Override + public void prepare(Callback callback) { + callback.onPrepared(this); + } + @Override public void maybeThrowPrepareError() throws IOException { loader.maybeThrowError(); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index c0902f5a84..385bebfb88 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -19,7 +19,6 @@ import android.net.Uri; import android.os.Handler; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.util.Assertions; @@ -95,13 +94,10 @@ public final class SingleSampleMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { Assertions.checkArgument(index == 0); - MediaPeriod mediaPeriod = new SingleSampleMediaPeriod(uri, dataSourceFactory, format, - minLoadableRetryCount, eventHandler, eventListener, eventSourceId); - callback.onPrepared(mediaPeriod); - return mediaPeriod; + return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount, + eventHandler, eventListener, eventSourceId); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java index e14daf0d20..e316215160 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.chunk; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.extractor.Extractor; @@ -150,7 +151,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput } @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java index 945a9b7487..388dc63899 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java @@ -111,7 +111,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad } @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { throw new IllegalStateException("Unexpected sample data in initialization chunk"); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index c5a18b9d2b..4a24c7c176 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -48,10 +48,10 @@ import java.util.List; private final EventDispatcher eventDispatcher; private final long elapsedRealtimeOffset; private final LoaderErrorThrower manifestLoaderErrorThrower; - private final Callback callback; private final Allocator allocator; private final TrackGroupArray trackGroups; + private Callback callback; private ChunkSampleStream[] sampleStreams; private CompositeSequenceableLoader sequenceableLoader; private DashManifest manifest; @@ -61,7 +61,7 @@ import java.util.List; public DashMediaPeriod(int id, DashManifest manifest, int index, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, EventDispatcher eventDispatcher, long elapsedRealtimeOffset, - LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { this.id = id; this.manifest = manifest; this.index = index; @@ -70,13 +70,11 @@ import java.util.List; this.eventDispatcher = eventDispatcher; this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; - this.callback = callback; this.allocator = allocator; sampleStreams = newSampleStreamArray(0); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); period = manifest.getPeriod(index); trackGroups = buildTrackGroups(period); - callback.onPrepared(this); } public void updateManifest(DashManifest manifest, int index) { @@ -97,6 +95,12 @@ import java.util.List; } } + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + @Override public void maybeThrowPrepareError() throws IOException { manifestLoaderErrorThrower.maybeThrowError(); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index a6e54ef4a1..766f1e0ebf 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; @@ -171,11 +170,10 @@ public final class DashMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + index, manifest, index, chunkSourceFactory, minLoadableRetryCount, eventDispatcher, elapsedRealtimeOffsetMs, loader, - callback, allocator); + allocator); periodsById.put(mediaPeriod.id, mediaPeriod); return mediaPeriod; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index e0bf7d00e6..143a0e995b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -654,9 +654,10 @@ public class DashManifestParser extends DefaultHandler } else if (MimeTypes.isVideo(containerMimeType)) { return MimeTypes.getVideoMediaMimeType(codecs); } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { - // We currently only support EIA-608 through RawCC - if (codecs != null && codecs.contains("eia608")) { - return MimeTypes.APPLICATION_EIA608; + // We currently only support CEA-608 through RawCC + if (codecs != null + && (codecs.contains("eia608") || codecs.contains("cea608"))) { + return MimeTypes.APPLICATION_CEA608; } return null; } else if (mimeTypeIsRawText(containerMimeType)) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 79205ccbc6..9be42e413f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; +import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory; import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster; import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; @@ -355,20 +356,22 @@ import java.util.Locale; timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, startTimeUs); // This flag ensures the change of pid between streams does not affect the sample queues. - int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE; + @DefaultStreamReaderFactory.WorkaroundFlags + int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE; String codecs = variants[newVariantIndex].format.codecs; if (!TextUtils.isEmpty(codecs)) { // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // exist. If we know from the codec attribute that they don't exist, then we can explicitly // ignore them even if they're declared. if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { - workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM; + workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_AAC_STREAM; } if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM; + workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM; } } - extractor = new TsExtractor(timestampAdjuster, workaroundFlags); + extractor = new TsExtractor(timestampAdjuster, + new DefaultStreamReaderFactory(workaroundFlags)); } else { // MPEG-2 TS segments, and we need to continue using the same extractor. extractor = previous.extractor; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 288412ccbb..ceb47771a6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -50,11 +50,11 @@ import java.util.List; /* package */ final class HlsMediaPeriod implements MediaPeriod, Loader.Callback>, HlsSampleStreamWrapper.Callback { + private final Uri manifestUri; private final DataSource.Factory dataSourceFactory; private final int minLoadableRetryCount; private final EventDispatcher eventDispatcher; private final MediaSource.Listener sourceListener; - private final Callback callback; private final Allocator allocator; private final IdentityHashMap streamWrapperIndices; private final TimestampAdjusterProvider timestampAdjusterProvider; @@ -62,7 +62,9 @@ import java.util.List; private final Handler continueLoadingHandler; private final Loader manifestFetcher; private final long preparePositionUs; + private final Runnable continueLoadingRunnable; + private Callback callback; private int pendingPrepareCount; private HlsPlaylist playlist; private boolean seenFirstTrackSelection; @@ -72,17 +74,16 @@ import java.util.List; private HlsSampleStreamWrapper[] sampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private CompositeSequenceableLoader sequenceableLoader; - private Runnable continueLoadingRunnable; public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory, int minLoadableRetryCount, EventDispatcher eventDispatcher, - MediaSource.Listener sourceListener, final Callback callback, Allocator allocator, + MediaSource.Listener sourceListener, Allocator allocator, long positionUs) { + this.manifestUri = manifestUri; this.dataSourceFactory = dataSourceFactory; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; this.sourceListener = sourceListener; - this.callback = callback; this.allocator = allocator; streamWrapperIndices = new IdentityHashMap<>(); timestampAdjusterProvider = new TimestampAdjusterProvider(); @@ -96,11 +97,6 @@ import java.util.List; callback.onContinueLoadingRequested(HlsMediaPeriod.this); } }; - - ParsingLoadable loadable = new ParsingLoadable<>( - dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser); - long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount); - eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); } public void release() { @@ -111,6 +107,15 @@ import java.util.List; } } + @Override + public void prepare(Callback callback) { + this.callback = callback; + ParsingLoadable loadable = new ParsingLoadable<>( + dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser); + long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); + } + @Override public void maybeThrowPrepareError() throws IOException { if (sampleStreamWrappers == null) { @@ -239,13 +244,7 @@ import java.util.List; eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); playlist = loadable.getResult(); - List sampleStreamWrapperList = buildSampleStreamWrappers(); - sampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrapperList.size()]; - sampleStreamWrapperList.toArray(sampleStreamWrappers); - pendingPrepareCount = sampleStreamWrappers.length; - for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { - sampleStreamWrapper.prepare(); - } + buildAndPrepareSampleStreamWrappers(); } @Override @@ -313,16 +312,16 @@ import java.util.List; // Internal methods. - private List buildSampleStreamWrappers() { - ArrayList sampleStreamWrappers = new ArrayList<>(); + private void buildAndPrepareSampleStreamWrappers() { String baseUri = playlist.baseUri; - if (playlist instanceof HlsMediaPlaylist) { HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] { HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)}; - sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, - null, null)); - return sampleStreamWrappers; + sampleStreamWrappers = new HlsSampleStreamWrapper[] { + buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)}; + pendingPrepareCount = 1; + sampleStreamWrappers[0].prepare(); + return; } HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; @@ -351,32 +350,37 @@ import java.util.List; } else { // Leave the enabled variants unchanged. They're likely either all video or all audio. } + List audioVariants = masterPlaylist.audios; + List subtitleVariants = masterPlaylist.subtitles; + sampleStreamWrappers = new HlsSampleStreamWrapper[(selectedVariants.isEmpty() ? 0 : 1) + + audioVariants.size() + subtitleVariants.size()]; + int currentWrapperIndex = 0; + pendingPrepareCount = sampleStreamWrappers.length; if (!selectedVariants.isEmpty()) { HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; selectedVariants.toArray(variants); - sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, - masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat)); + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, + baseUri, variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat); + sampleStreamWrapper.prepare(); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; } - // Build the audio stream wrapper if applicable. - List audioVariants = masterPlaylist.audios; - if (!audioVariants.isEmpty()) { - HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[audioVariants.size()]; - audioVariants.toArray(variants); - sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null, - null)); + // Build audio stream wrappers. + for (int i = 0; i < audioVariants.size(); i++) { + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, + baseUri, new HlsMasterPlaylist.HlsUrl[] {audioVariants.get(i)}, null, null); + sampleStreamWrapper.prepare(); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; } - // Build the text stream wrapper if applicable. - List subtitleVariants = masterPlaylist.subtitles; - if (!subtitleVariants.isEmpty()) { - HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[subtitleVariants.size()]; - subtitleVariants.toArray(variants); - sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null, - null)); + // Build subtitle stream wrappers. + for (int i = 0; i < subtitleVariants.size(); i++) { + HlsMasterPlaylist.HlsUrl url = subtitleVariants.get(i); + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, + baseUri, new HlsMasterPlaylist.HlsUrl[] {url}, null, null); + sampleStreamWrapper.prepareSingleTrack(url.format); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; } - - return sampleStreamWrappers; } private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri, diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 72a9fd6c0c..b8b6c033b3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.upstream.Allocator; @@ -73,11 +72,10 @@ public final class HlsMediaSource implements MediaSource { } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { Assertions.checkArgument(index == 0); return new HlsMediaPeriod(manifestUri, dataSourceFactory, minLoadableRetryCount, - eventDispatcher, sourceListener, callback, allocator, positionUs); + eventDispatcher, sourceListener, allocator, positionUs); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 83de29c039..1f9fd11c3f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.hls; +import android.os.Handler; +import android.text.TextUtils; import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -80,13 +82,15 @@ import java.util.LinkedList; private final HlsChunkSource.HlsChunkHolder nextChunkHolder; private final SparseArray sampleQueues; private final LinkedList mediaChunks; + private final Runnable maybeFinishPrepareRunnable; + private final Handler handler; - private volatile boolean sampleQueuesBuilt; - + private boolean sampleQueuesBuilt; private boolean prepared; private int enabledTrackCount; private Format downstreamTrackFormat; private int upstreamChunkUid; + private boolean released; // Tracks are complicated in HLS. See documentation of buildTracks for details. // Indexed by track (as exposed by this source). @@ -129,6 +133,13 @@ import java.util.LinkedList; nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); sampleQueues = new SparseArray<>(); mediaChunks = new LinkedList<>(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + handler = new Handler(); lastSeekPositionUs = positionUs; pendingResetPositionUs = positionUs; } @@ -137,6 +148,15 @@ import java.util.LinkedList; continueLoading(lastSeekPositionUs); } + /** + * Prepares a sample stream wrapper for which the master playlist provides enough information to + * prepare. + */ + public void prepareSingleTrack(Format format) { + track(0).format(format); + endTracks(); + } + public void maybeThrowPrepareError() throws IOException { maybeThrowError(); } @@ -245,6 +265,8 @@ import java.util.LinkedList; sampleQueues.valueAt(i).disable(); } loader.release(); + handler.removeCallbacksAndMessages(null); + released = true; } public long getLargestQueuedTimestampUs() { @@ -454,7 +476,7 @@ import java.util.LinkedList; @Override public void endTracks() { sampleQueuesBuilt = true; - maybeFinishPrepare(); + handler.post(maybeFinishPrepareRunnable); } @Override @@ -462,17 +484,17 @@ import java.util.LinkedList; // Do nothing. } - // UpstreamFormatChangedListener implementation. + // UpstreamFormatChangedListener implementation. Called by the loading thread. @Override public void onUpstreamFormatChanged(Format format) { - maybeFinishPrepare(); + handler.post(maybeFinishPrepareRunnable); } // Internal methods. private void maybeFinishPrepare() { - if (prepared || !sampleQueuesBuilt) { + if (released || prepared || !sampleQueuesBuilt) { return; } int sampleQueueCount = sampleQueues.size(); @@ -558,7 +580,7 @@ import java.util.LinkedList; if (i == primaryExtractorTrackIndex) { Format[] formats = new Format[chunkSourceTrackCount]; for (int j = 0; j < chunkSourceTrackCount; j++) { - formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat); + formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat); } trackGroups[i] = new TrackGroup(formats); primaryTrackGroupIndex = i; @@ -567,11 +589,11 @@ import java.util.LinkedList; if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { trackFormat = muxedAudioFormat; - } else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) { + } else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) { trackFormat = muxedCaptionFormat; } } - trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat)); + trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat)); } } this.trackGroups = new TrackGroupArray(trackGroups); @@ -590,18 +612,25 @@ import java.util.LinkedList; } /** - * Derives a sample format corresponding to a given container format, by combining it with sample - * level information obtained from a second sample format. + * Derives a track format corresponding to a given container format, by combining it with sample + * level information obtained from the samples. * - * @param containerFormat The container format for which the sample format should be derived. + * @param containerFormat The container format for which the track format should be derived. * @param sampleFormat A sample format from which to obtain sample level information. - * @return The derived sample format. + * @return The derived track format. */ - private static Format getSampleFormat(Format containerFormat, Format sampleFormat) { + private static Format deriveFormat(Format containerFormat, Format sampleFormat) { if (containerFormat == null) { return sampleFormat; } - return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, + String codecs = null; + int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); + if (sampleTrackType == C.TRACK_TYPE_AUDIO) { + codecs = getAudioCodecs(containerFormat.codecs); + } else if (sampleTrackType == C.TRACK_TYPE_VIDEO) { + codecs = getVideoCodecs(containerFormat.codecs); + } + return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate, containerFormat.width, containerFormat.height, containerFormat.selectionFlags, containerFormat.language); } @@ -614,4 +643,29 @@ import java.util.LinkedList; return pendingResetPositionUs != C.TIME_UNSET; } + private static String getAudioCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO); + } + + private static String getVideoCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); + } + + private static String getCodecsOfType(String codecs, int trackType) { + if (TextUtils.isEmpty(codecs)) { + return null; + } + String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); + StringBuilder builder = new StringBuilder(); + for (String codec : codecArray) { + if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); + } + } + return builder.length() > 0 ? builder.toString() : null; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java index 98f4b20723..fb62d9978e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -15,18 +15,29 @@ */ package com.google.android.exoplayer2.source.hls.playlist; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents an HLS playlist. */ public abstract class HlsPlaylist { + /** + * The type of playlist. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_MASTER, TYPE_MEDIA}) + public @interface Type {} public static final int TYPE_MASTER = 0; public static final int TYPE_MEDIA = 1; public final String baseUri; + @Type public final int type; - protected HlsPlaylist(String baseUri, int type) { + protected HlsPlaylist(String baseUri, @Type int type) { this.baseUri = baseUri; this.type = type; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 96f69b69db..d5e5267820 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -62,7 +62,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser[] sampleStreams; private CompositeSequenceableLoader sequenceableLoader; public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, EventDispatcher eventDispatcher, - LoaderErrorThrower manifestLoaderErrorThrower, Callback callback, Allocator allocator) { + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { this.chunkSourceFactory = chunkSourceFactory; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; - this.callback = callback; this.allocator = allocator; trackGroups = buildTrackGroups(manifest); @@ -77,7 +76,6 @@ import java.util.ArrayList; this.manifest = manifest; sampleStreams = newSampleStreamArray(0); sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); - callback.onPrepared(this); } public void updateManifest(SsManifest manifest) { @@ -94,6 +92,12 @@ import java.util.ArrayList; } } + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + @Override public void maybeThrowPrepareError() throws IOException { manifestLoaderErrorThrower.maybeThrowError(); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index ef8129e228..d328e5ecf2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; @@ -122,11 +121,10 @@ public final class SsMediaSource implements MediaSource, } @Override - public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator, - long positionUs) { + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { Assertions.checkArgument(index == 0); SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, - eventDispatcher, manifestLoader, callback, allocator); + eventDispatcher, manifestLoader, allocator); mediaPeriods.add(period); return period; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java index b7b6c701bf..b7a75ed679 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java @@ -18,35 +18,41 @@ package com.google.android.exoplayer2.text; import android.annotation.TargetApi; import android.graphics.Color; import android.graphics.Typeface; +import android.support.annotation.IntDef; import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A compatibility wrapper for {@link CaptionStyle}. */ public final class CaptionStyleCompat { + /** + * The type of edge, which may be none. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED}) + public @interface EdgeType {} /** * Edge type value specifying no character edges. */ public static final int EDGE_TYPE_NONE = 0; - /** * Edge type value specifying uniformly outlined character edges. */ public static final int EDGE_TYPE_OUTLINE = 1; - /** * Edge type value specifying drop-shadowed character edges. */ public static final int EDGE_TYPE_DROP_SHADOW = 2; - /** * Edge type value specifying raised bevel character edges. */ public static final int EDGE_TYPE_RAISED = 3; - /** * Edge type value specifying depressed bevel character edges. */ @@ -88,6 +94,7 @@ public final class CaptionStyleCompat { *

  • {@link #EDGE_TYPE_DEPRESSED} * */ + @EdgeType public final int edgeType; /** @@ -126,8 +133,8 @@ public final class CaptionStyleCompat { * @param edgeColor See {@link #edgeColor}. * @param typeface See {@link #typeface}. */ - public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType, - int edgeColor, Typeface typeface) { + public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, + @EdgeType int edgeType, int edgeColor, Typeface typeface) { this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; this.windowColor = windowColor; @@ -137,6 +144,7 @@ public final class CaptionStyleCompat { } @TargetApi(19) + @SuppressWarnings("ResourceType") private static CaptionStyleCompat createFromCaptionStyleV19( CaptioningManager.CaptionStyle captionStyle) { return new CaptionStyleCompat( @@ -145,6 +153,7 @@ public final class CaptionStyleCompat { } @TargetApi(21) + @SuppressWarnings("ResourceType") private static CaptionStyleCompat createFromCaptionStyleV21( CaptioningManager.CaptionStyle captionStyle) { return new CaptionStyleCompat( diff --git a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java index 01d5b53c86..93b1dc1d9a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -15,7 +15,10 @@ */ package com.google.android.exoplayer2.text; +import android.support.annotation.IntDef; import android.text.Layout.Alignment; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Contains information about a specific cue, including textual content and formatting data. @@ -26,6 +29,13 @@ public class Cue { * An unset position or width. */ public static final float DIMEN_UNSET = Float.MIN_VALUE; + + /** + * The type of anchor, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) + public @interface AnchorType {} /** * An unset anchor or line type value. */ @@ -44,6 +54,13 @@ public class Cue { * box. */ public static final int ANCHOR_TYPE_END = 2; + + /** + * The type of line, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) + public @interface LineType {} /** * Value for {@link #lineType} when {@link #line} is a fractional position. */ @@ -83,6 +100,7 @@ public class Cue { * -1). For horizontal text the size of the first line of the cue is its height, and the start * and end of the viewport are the top and bottom respectively. */ + @LineType public final int lineType; /** * The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, @@ -92,6 +110,7 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box * respectively. */ + @AnchorType public final int lineAnchor; /** * The fractional position of the {@link #positionAnchor} of the cue box within the viewport in @@ -110,6 +129,7 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box * respectively. */ + @AnchorType public final int positionAnchor; /** * The size of the cue box in the writing direction specified as a fraction of the viewport size @@ -137,8 +157,8 @@ public class Cue { * @param positionAnchor See {@link #positionAnchor}. * @param size See {@link #size}. */ - public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, int lineAnchor, - float position, int positionAnchor, float size) { + public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) { this.text = text; this.textAlignment = textAlignment; this.line = line; diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index 918d0b37b7..2cbc1ab622 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.text; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.text.eia608.Eia608Decoder; +import com.google.android.exoplayer2.text.cea.Cea608Decoder; import com.google.android.exoplayer2.text.subrip.SubripDecoder; import com.google.android.exoplayer2.text.ttml.TtmlDecoder; import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; @@ -57,7 +57,7 @@ public interface SubtitleDecoderFactory { *
  • TTML ({@link TtmlDecoder})
  • *
  • SubRip ({@link SubripDecoder})
  • *
  • TX3G ({@link Tx3gDecoder})
  • - *
  • Eia608 ({@link Eia608Decoder})
  • + *
  • Cea608 ({@link Cea608Decoder})
  • * */ SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { @@ -93,8 +93,8 @@ public interface SubtitleDecoderFactory { return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder"); case MimeTypes.APPLICATION_TX3G: return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); - case MimeTypes.APPLICATION_EIA608: - return Class.forName("com.google.android.exoplayer2.text.eia608.Eia608Decoder"); + case MimeTypes.APPLICATION_CEA608: + return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder"); default: return null; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Decoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java similarity index 73% rename from library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Decoder.java rename to library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index b5249cde78..2715b0cbe0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -13,26 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.text.eia608; +package com.google.android.exoplayer2.text.cea; import android.text.TextUtils; -import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoder; -import com.google.android.exoplayer2.text.SubtitleDecoderException; import com.google.android.exoplayer2.text.SubtitleInputBuffer; -import com.google.android.exoplayer2.text.SubtitleOutputBuffer; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableByteArray; -import java.util.LinkedList; -import java.util.TreeSet; /** - * A {@link SubtitleDecoder} for EIA-608 (also known as "line 21 captions" and "CEA-608"). + * A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608"). */ -public final class Eia608Decoder implements SubtitleDecoder { +public final class Cea608Decoder extends CeaDecoder { - private static final int NUM_INPUT_BUFFERS = 10; - private static final int NUM_OUTPUT_BUFFERS = 2; + private static final int NTSC_CC_FIELD_1 = 0x00; + private static final int CC_VALID_FLAG = 0x04; private static final int PAYLOAD_TYPE_CC = 4; private static final int COUNTRY_CODE = 0xB5; @@ -159,18 +155,10 @@ public final class Eia608Decoder implements SubtitleDecoder { 0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518 }; - private final LinkedList availableInputBuffers; - private final LinkedList availableOutputBuffers; - private final TreeSet queuedInputBuffers; - private final ParsableByteArray ccData; private final StringBuilder captionStringBuilder; - private long playbackPositionUs; - - private SubtitleInputBuffer dequeuedInputBuffer; - private int captionMode; private int captionRowCount; private String captionString; @@ -181,17 +169,7 @@ public final class Eia608Decoder implements SubtitleDecoder { private byte repeatableControlCc1; private byte repeatableControlCc2; - public Eia608Decoder() { - availableInputBuffers = new LinkedList<>(); - for (int i = 0; i < NUM_INPUT_BUFFERS; i++) { - availableInputBuffers.add(new SubtitleInputBuffer()); - } - availableOutputBuffers = new LinkedList<>(); - for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { - availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this)); - } - queuedInputBuffers = new TreeSet<>(); - + public Cea608Decoder() { ccData = new ParsableByteArray(); captionStringBuilder = new StringBuilder(); @@ -202,101 +180,20 @@ public final class Eia608Decoder implements SubtitleDecoder { @Override public String getName() { - return "Eia608Decoder"; - } - - @Override - public void setPositionUs(long positionUs) { - playbackPositionUs = positionUs; - } - - @Override - public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException { - Assertions.checkState(dequeuedInputBuffer == null); - if (availableInputBuffers.isEmpty()) { - return null; - } - dequeuedInputBuffer = availableInputBuffers.pollFirst(); - return dequeuedInputBuffer; - } - - @Override - public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException { - Assertions.checkArgument(inputBuffer != null); - Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); - queuedInputBuffers.add(inputBuffer); - dequeuedInputBuffer = null; - } - - @Override - public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException { - if (availableOutputBuffers.isEmpty()) { - return null; - } - - // iterate through all available input buffers whose timestamps are less than or equal - // to the current playback position; processing input buffers for future content should - // be deferred until they would be applicable - while (!queuedInputBuffers.isEmpty() - && queuedInputBuffers.first().timeUs <= playbackPositionUs) { - SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); - - // If the input buffer indicates we've reached the end of the stream, we can - // return immediately with an output buffer propagating that - if (inputBuffer.isEndOfStream()) { - SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); - outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); - releaseInputBuffer(inputBuffer); - return outputBuffer; - } - - decode(inputBuffer); - - // check if we have any caption updates to report - if (!TextUtils.equals(captionString, lastCaptionString)) { - lastCaptionString = captionString; - if (!inputBuffer.isDecodeOnly()) { - SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); - outputBuffer.setContent(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0); - releaseInputBuffer(inputBuffer); - return outputBuffer; - } - } - - releaseInputBuffer(inputBuffer); - } - - return null; - } - - private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) { - inputBuffer.clear(); - availableInputBuffers.add(inputBuffer); - } - - protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) { - outputBuffer.clear(); - availableOutputBuffers.add(outputBuffer); + return "Cea608Decoder"; } @Override public void flush() { + super.flush(); setCaptionMode(CC_MODE_UNKNOWN); captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; - playbackPositionUs = 0; captionStringBuilder.setLength(0); captionString = null; lastCaptionString = null; repeatableControlSet = false; repeatableControlCc1 = 0; repeatableControlCc2 = 0; - while (!queuedInputBuffers.isEmpty()) { - releaseInputBuffer(queuedInputBuffers.pollFirst()); - } - if (dequeuedInputBuffer != null) { - releaseInputBuffer(dequeuedInputBuffer); - dequeuedInputBuffer = null; - } } @Override @@ -304,14 +201,33 @@ public final class Eia608Decoder implements SubtitleDecoder { // Do nothing } - private void decode(SubtitleInputBuffer inputBuffer) { + @Override + protected boolean isNewSubtitleDataAvailable() { + return !TextUtils.equals(captionString, lastCaptionString); + } + + @Override + protected Subtitle createSubtitle() { + lastCaptionString = captionString; + return new CeaSubtitle(new Cue(captionString)); + } + + @Override + protected void decode(SubtitleInputBuffer inputBuffer) { ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); boolean captionDataProcessed = false; boolean isRepeatableControl = false; while (ccData.bytesLeft() > 0) { + byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07); byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); + // Only examine valid NTSC_CC_FIELD_1 packets + if (ccTypeAndValid != (CC_VALID_FLAG | NTSC_CC_FIELD_1)) { + // TODO: Add support for NTSC_CC_FIELD_2 packets + continue; + } + // Ignore empty captions. if (ccData1 == 0 && ccData2 == 0) { continue; @@ -320,26 +236,33 @@ public final class Eia608Decoder implements SubtitleDecoder { captionDataProcessed = true; // Special North American character set. + // ccData1 - P|0|0|1|C|0|0|1 // ccData2 - P|0|1|1|X|X|X|X if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) { + // TODO: Make use of the channel bit captionStringBuilder.append(getSpecialChar(ccData2)); continue; } - // Extended Spanish/Miscellaneous and French character set. + // Extended Western European character set. + // ccData1 - P|0|0|1|C|0|1|S // ccData2 - P|0|1|X|X|X|X|X - if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) { - backspace(); // Remove standard equivalent of the special extended char. - captionStringBuilder.append(getExtendedEsFrChar(ccData2)); - continue; - } + if ((ccData2 & 0x60) == 0x20) { + // Extended Spanish/Miscellaneous and French character set (S = 0). + if (ccData1 == 0x12 || ccData1 == 0x1A) { + // TODO: Make use of the channel bit + backspace(); // Remove standard equivalent of the special extended char. + captionStringBuilder.append(getExtendedEsFrChar(ccData2)); + continue; + } - // Extended Portuguese and German/Danish character set. - // ccData2 - P|0|1|X|X|X|X|X - if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) { - backspace(); // Remove standard equivalent of the special extended char. - captionStringBuilder.append(getExtendedPtDeChar(ccData2)); - continue; + // Extended Portuguese and German/Danish character set (S = 1). + if (ccData1 == 0x13 || ccData1 == 0x1B) { + // TODO: Make use of the channel bit + backspace(); // Remove standard equivalent of the special extended char. + captionStringBuilder.append(getExtendedPtDeChar(ccData2)); + continue; + } } // Control character. @@ -367,15 +290,17 @@ public final class Eia608Decoder implements SubtitleDecoder { private boolean handleCtrl(byte cc1, byte cc2) { boolean isRepeatableControl = isRepeatable(cc1); - if (isRepeatableControl && repeatableControlSet - && repeatableControlCc1 == cc1 - && repeatableControlCc2 == cc2) { - repeatableControlSet = false; - return true; - } else if (isRepeatableControl) { - repeatableControlSet = true; - repeatableControlCc1 = cc1; - repeatableControlCc2 = cc2; + if (isRepeatableControl) { + if (repeatableControlSet + && repeatableControlCc1 == cc1 + && repeatableControlCc2 == cc2) { + repeatableControlSet = false; + return true; + } else { + repeatableControlSet = true; + repeatableControlCc1 = cc1; + repeatableControlCc2 = cc2; + } } if (isMiscCode(cc1, cc2)) { handleMiscCode(cc2); @@ -526,16 +451,16 @@ public final class Eia608Decoder implements SubtitleDecoder { } /** - * Inspects an sei message to determine whether it contains EIA-608. + * Inspects an sei message to determine whether it contains CEA-608. *

    * The position of {@code payload} is left unchanged. * * @param payloadType The payload type of the message. * @param payloadLength The length of the payload. * @param payload A {@link ParsableByteArray} containing the payload. - * @return Whether the sei message contains EIA-608. + * @return Whether the sei message contains CEA-608. */ - public static boolean isSeiMessageEia608(int payloadType, int payloadLength, + public static boolean isSeiMessageCea608(int payloadType, int payloadLength, ParsableByteArray payload) { if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { return false; diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java new file mode 100644 index 0000000000..ae92d7fab8 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.cea; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.text.Subtitle; +import com.google.android.exoplayer2.text.SubtitleDecoder; +import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.text.SubtitleInputBuffer; +import com.google.android.exoplayer2.text.SubtitleOutputBuffer; +import com.google.android.exoplayer2.util.Assertions; +import java.util.LinkedList; +import java.util.TreeSet; + +/** + * Base class for subtitle parsers for CEA captions. + */ +/* package */ abstract class CeaDecoder implements SubtitleDecoder { + + private static final int NUM_INPUT_BUFFERS = 10; + private static final int NUM_OUTPUT_BUFFERS = 2; + + private final LinkedList availableInputBuffers; + private final LinkedList availableOutputBuffers; + private final TreeSet 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); + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608SubtitleOutputBuffer.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java similarity index 72% rename from library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608SubtitleOutputBuffer.java rename to library/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java index 7e5ba062ab..4cc32bb9e4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608SubtitleOutputBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java @@ -13,22 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.text.eia608; +package com.google.android.exoplayer2.text.cea; -import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleOutputBuffer; /** - * A {@link Subtitle} output from an {@link Eia608Decoder}. + * A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s. */ -/* package */ final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer { +public final class CeaOutputBuffer extends SubtitleOutputBuffer { - private Eia608Decoder owner; + private final CeaDecoder owner; /** * @param owner The decoder that owns this buffer. */ - public Eia608SubtitleOutputBuffer(Eia608Decoder owner) { + public CeaOutputBuffer(CeaDecoder owner) { super(); this.owner = owner; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Subtitle.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java similarity index 71% rename from library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Subtitle.java rename to library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java index 6b27004174..5becefe106 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/eia608/Eia608Subtitle.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java @@ -13,26 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.text.eia608; +package com.google.android.exoplayer2.text.cea; -import android.text.TextUtils; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; import java.util.Collections; import java.util.List; /** - * A representation of an EIA-608 subtitle. + * A representation of a CEA subtitle. */ -/* package */ final class Eia608Subtitle implements Subtitle { +/* package */ final class CeaSubtitle implements Subtitle { - private final String text; + private final List cues; /** - * @param text The subtitle text. + * @param cue The subtitle cue. */ - public Eia608Subtitle(String text) { - this.text = text; + public CeaSubtitle(Cue cue) { + if (cue == null) { + cues = Collections.emptyList(); + } else { + cues = Collections.singletonList(cue); + } } @Override @@ -52,11 +55,8 @@ import java.util.List; @Override public List getCues(long timeUs) { - if (TextUtils.isEmpty(text)) { - return Collections.emptyList(); - } else { - return Collections.singletonList(new Cue(text)); - } + return cues; + } } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java index 75fe1154a9..5f30834b4d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.text.Cue; public final float position; public final float line; + @Cue.LineType public final int lineType; public final float width; @@ -31,7 +32,7 @@ import com.google.android.exoplayer2.text.Cue; this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); } - public TtmlRegion(float position, float line, int lineType, float width) { + public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) { this.position = position; this.line = line; this.lineType = lineType; diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java index aa87af0e57..e4c36be03a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java @@ -16,8 +16,11 @@ package com.google.android.exoplayer2.text.ttml; import android.graphics.Typeface; +import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Style object of a TtmlNode @@ -26,15 +29,25 @@ import com.google.android.exoplayer2.util.Assertions; public static final int UNSPECIFIED = -1; + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} private static final int OFF = 0; private static final int ON = 1; @@ -43,10 +56,15 @@ import com.google.android.exoplayer2.util.Assertions; private boolean hasFontColor; private int backgroundColor; private boolean hasBackgroundColor; + @OptionalBoolean private int linethrough; + @OptionalBoolean private int underline; + @OptionalBoolean private int bold; + @OptionalBoolean private int italic; + @FontSizeUnit private int fontSizeUnit; private float fontSize; private String id; @@ -67,12 +85,13 @@ import com.google.android.exoplayer2.util.Assertions; * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} * or {@link #STYLE_BOLD_ITALIC}. */ + @StyleFlags public int getStyle() { if (bold == UNSPECIFIED && italic == UNSPECIFIED) { return UNSPECIFIED; } - return (bold != UNSPECIFIED ? bold : STYLE_NORMAL) - | (italic != UNSPECIFIED ? italic : STYLE_NORMAL); + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); } public boolean isLinethrough() { @@ -95,6 +114,18 @@ import com.google.android.exoplayer2.util.Assertions; return this; } + public TtmlStyle setBold(boolean bold) { + Assertions.checkState(inheritableStyle == null); + this.bold = bold ? ON : OFF; + return this; + } + + public TtmlStyle setItalic(boolean italic) { + Assertions.checkState(inheritableStyle == null); + this.italic = italic ? ON : OFF; + return this; + } + public String getFontFamily() { return fontFamily; } @@ -140,18 +171,6 @@ import com.google.android.exoplayer2.util.Assertions; return hasBackgroundColor; } - public TtmlStyle setBold(boolean isBold) { - Assertions.checkState(inheritableStyle == null); - bold = isBold ? STYLE_BOLD : STYLE_NORMAL; - return this; - } - - public TtmlStyle setItalic(boolean isItalic) { - Assertions.checkState(inheritableStyle == null); - italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL; - return this; - } - /** * Inherits from an ancestor style. Properties like tts:backgroundColor which * are not inheritable are not inherited as well as properties which are already set locally @@ -236,6 +255,7 @@ import com.google.android.exoplayer2.util.Assertions; return this; } + @FontSizeUnit public int getFontSizeUnit() { return fontSizeUnit; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java index 169bc6a8bf..157174a8f0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -16,8 +16,11 @@ package com.google.android.exoplayer2.text.webvtt; import android.graphics.Typeface; +import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -32,15 +35,25 @@ import java.util.List; public static final int UNSPECIFIED = -1; + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} private static final int OFF = 0; private static final int ON = 1; @@ -56,10 +69,15 @@ import java.util.List; private boolean hasFontColor; private int backgroundColor; private boolean hasBackgroundColor; + @OptionalBoolean private int linethrough; + @OptionalBoolean private int underline; + @OptionalBoolean private int bold; + @OptionalBoolean private int italic; + @FontSizeUnit private int fontSizeUnit; private float fontSize; private Layout.Alignment textAlign; @@ -144,12 +162,13 @@ import java.util.List; * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} * or {@link #STYLE_BOLD_ITALIC}. */ + @StyleFlags public int getStyle() { if (bold == UNSPECIFIED && italic == UNSPECIFIED) { return UNSPECIFIED; } - return (bold != UNSPECIFIED ? bold : STYLE_NORMAL) - | (italic != UNSPECIFIED ? italic : STYLE_NORMAL); + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); } public boolean isLinethrough() { @@ -169,6 +188,15 @@ import java.util.List; this.underline = underline ? ON : OFF; return this; } + public WebvttCssStyle setBold(boolean bold) { + this.bold = bold ? ON : OFF; + return this; + } + + public WebvttCssStyle setItalic(boolean italic) { + this.italic = italic ? ON : OFF; + return this; + } public String getFontFamily() { return fontFamily; @@ -213,16 +241,6 @@ import java.util.List; return hasBackgroundColor; } - public WebvttCssStyle setBold(boolean isBold) { - bold = isBold ? STYLE_BOLD : STYLE_NORMAL; - return this; - } - - public WebvttCssStyle setItalic(boolean isItalic) { - italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL; - return this; - } - public Layout.Alignment getTextAlign() { return textAlign; } @@ -242,6 +260,7 @@ import java.util.List; return this; } + @FontSizeUnit public int getFontSizeUnit() { return fontSizeUnit; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java index f1ce0e28fc..295fdc656f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java @@ -37,8 +37,9 @@ import com.google.android.exoplayer2.text.Cue; Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); } - public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment, - float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) { + public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment, + float line, @Cue.LineType int lineType, @Cue.AnchorType int lineAnchor, float position, + @Cue.AnchorType int positionAnchor, float width) { super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width); this.startTime = startTime; this.endTime = endTime; diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 4f76983d15..81d79ac055 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -18,22 +18,301 @@ package com.google.android.exoplayer2.trackselection; import android.content.Context; import android.graphics.Point; import android.os.Handler; +import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; /** - * A {@link MappingTrackSelector} that allows configuration of common parameters. + * A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call + * the methods of this class from the application thread. See {@link Parameters#Parameters()} for + * default selection parameters. */ public class DefaultTrackSelector extends MappingTrackSelector { + /** + * Holder for available configurations for the {@link DefaultTrackSelector}. + */ + public static final class Parameters { + + // Audio. + public final String preferredAudioLanguage; + + // Text. + public final String preferredTextLanguage; + + // Video. + public final boolean allowMixedMimeAdaptiveness; + public final boolean allowNonSeamlessAdaptiveness; + public final int maxVideoWidth; + public final int maxVideoHeight; + public final boolean exceedVideoConstraintsIfNecessary; + public final int viewportWidth; + public final int viewportHeight; + public final boolean orientationMayChange; + + /** + * Constructor with default selection parameters: + *

      + *
    • No preferred audio language is set.
    • + *
    • No preferred text language is set.
    • + *
    • Adaptation between different mime types is not allowed.
    • + *
    • Non seamless adaptation is allowed.
    • + *
    • No max limit for video width/height.
    • + *
    • Video constraints are ignored if no supported selection can be made otherwise.
    • + *
    • No viewport width/height constraints are set.
    • + *
    + */ + public Parameters() { + this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE, + Integer.MAX_VALUE, true); + } + + /** + * @param preferredAudioLanguage The preferred language for audio, as well as for forced text + * tracks as defined by RFC 5646. {@code null} to select the default track, or first track + * if there's no default. + * @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646. + * {@code null} to select the default track, or first track if there's no default. + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @param maxVideoWidth Maximum allowed video width. + * @param maxVideoHeight Maximum allowed video height. + * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections + * can be made otherwise. False to force constraints anyway. + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + */ + public Parameters(String preferredAudioLanguage, String preferredTextLanguage, + boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness, + int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary, + int viewportWidth, int viewportHeight, boolean orientationMayChange) { + this.preferredAudioLanguage = preferredAudioLanguage; + this.preferredTextLanguage = preferredTextLanguage; + this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + this.maxVideoWidth = maxVideoWidth; + this.maxVideoHeight = maxVideoHeight; + this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + this.orientationMayChange = orientationMayChange; + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + * + * @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or first track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + */ + public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) { + preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); + if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for text tracks. + * + * @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or no track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for text tracks. + */ + public Parameters withPreferredTextLanguage(String preferredTextLanguage) { + preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); + if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + * + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + */ + public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { + if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance. + * + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @return A {@link Parameters} instance with the provided seamless adaptiveness allowance. + */ + public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { + if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided max video size. + * + * @param maxVideoWidth The max video width. + * @param maxVideoHeight The max video width. + * @return A {@link Parameters} instance with the provided max video size. + */ + public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { + if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Equivalent to {@code withMaxVideoSize(1279, 719)}. + * + * @return A {@link Parameters} instance with maximum standard definition as maximum video size. + */ + public Parameters withMaxVideoSizeSd() { + return withMaxVideoSize(1279, 719); + } + + /** + * Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. + * + * @return A {@link Parameters} instance without video size constraints. + */ + public Parameters withoutVideoSizeConstraints() { + return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns a {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + * + * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections + * can be made otherwise. False to force constraints anyway. + * @return A {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + */ + public Parameters withExceedVideoConstraintsIfNecessary( + boolean exceedVideoConstraintsIfNecessary) { + if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided viewport size. + * + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance with the provided viewport size. + */ + public Parameters withViewportSize(int viewportWidth, int viewportHeight, + boolean orientationMayChange) { + if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight + && orientationMayChange == this.orientationMayChange) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + * + * @param context The context to obtain the viewport size from. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + */ + public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) { + // Assume the viewport is fullscreen. + Point viewportSize = Util.getPhysicalDisplaySize(context); + return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange); + } + + /** + * Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. + * + * @return A {@link Parameters} instance without viewport size constraints. + */ + public Parameters withoutViewportSizeConstraints() { + return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Parameters other = (Parameters) obj; + return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness + && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness + && maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight + && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary + && orientationMayChange == other.orientationMayChange + && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight + && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) + && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage); + } + + @Override + public int hashCode() { + int result = preferredAudioLanguage.hashCode(); + result = 31 * result + preferredTextLanguage.hashCode(); + result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); + result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); + result = 31 * result + maxVideoWidth; + result = 31 * result + maxVideoHeight; + result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (orientationMayChange ? 1 : 0); + result = 31 * result + viewportWidth; + result = 31 * result + viewportHeight; + return result; + } + + } + /** * If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the * corresponding viewport dimension, then the video is considered as filling the viewport (in that @@ -43,22 +322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private static final int[] NO_TRACKS = new int[0]; private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; - - // Audio. - private String preferredAudioLanguage; - - // Text. - private String preferredTextLanguage; - - // Video. - private boolean allowMixedMimeAdaptiveness; - private boolean allowNonSeamlessAdaptiveness; - private int maxVideoWidth; - private int maxVideoHeight; - private boolean exceedVideoConstraintsIfNecessary; - private boolean orientationMayChange; - private int viewportWidth; - private int viewportHeight; + private final AtomicReference params; /** * Constructs an instance that does not support adaptive video. @@ -82,142 +346,28 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { super(eventHandler); this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; - allowNonSeamlessAdaptiveness = true; - exceedVideoConstraintsIfNecessary = true; - maxVideoWidth = Integer.MAX_VALUE; - maxVideoHeight = Integer.MAX_VALUE; - viewportWidth = Integer.MAX_VALUE; - viewportHeight = Integer.MAX_VALUE; - orientationMayChange = true; + params = new AtomicReference<>(new Parameters()); } /** - * Sets the preferred language for audio, as well as for forced text tracks. + * Atomically sets the provided parameters for track selection. * - * @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to - * select the default track, or first track if there's no default. + * @param params The parameters for track selection. */ - public void setPreferredLanguages(String preferredAudioLanguage) { - preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); - if (!Util.areEqual(this.preferredAudioLanguage, preferredAudioLanguage)) { - this.preferredAudioLanguage = preferredAudioLanguage; + public void setParameters(Parameters params) { + if (!this.params.get().equals(params)) { + this.params.set(Assertions.checkNotNull(params)); invalidate(); } } /** - * Sets the preferred language for text tracks. + * Gets the current selection parameters. * - * @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to - * select the default track, or no track if there's no default. + * @return The current selection parameters. */ - public void setPreferredTextLanguage(String preferredTextLanguage) { - preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); - if (!Util.areEqual(this.preferredTextLanguage, preferredTextLanguage)) { - this.preferredTextLanguage = preferredTextLanguage; - invalidate(); - } - } - - /** - * Sets whether to allow selections to contain mixed mime types. - * - * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. - */ - public void allowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { - if (this.allowMixedMimeAdaptiveness != allowMixedMimeAdaptiveness) { - this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; - invalidate(); - } - } - - /** - * Sets whether non-seamless adaptation is allowed. - * - * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. - */ - public void allowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { - if (this.allowNonSeamlessAdaptiveness != allowNonSeamlessAdaptiveness) { - this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; - invalidate(); - } - } - - /** - * Sets the maximum allowed size for video tracks. - * - * @param maxVideoWidth Maximum allowed width. - * @param maxVideoHeight Maximum allowed height. - */ - public void setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { - if (this.maxVideoWidth != maxVideoWidth || this.maxVideoHeight != maxVideoHeight) { - this.maxVideoWidth = maxVideoWidth; - this.maxVideoHeight = maxVideoHeight; - invalidate(); - } - } - - /** - * Equivalent to {@code setMaxVideoSize(1279, 719)}. - */ - public void setMaxVideoSizeSd() { - setMaxVideoSize(1279, 719); - } - - /** - * Equivalent to {@code setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. - */ - public void clearMaxVideoSize() { - setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); - } - - /** - * Sets whether video constraints should be ignored when no selection can be made otherwise. - * - * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections - * can be made otherwise. False to force constraints anyway. - */ - public void setExceedVideoConstraintsIfNecessary(boolean exceedVideoConstraintsIfNecessary) { - if (this.exceedVideoConstraintsIfNecessary != exceedVideoConstraintsIfNecessary) { - this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; - invalidate(); - } - } - - /** - * Sets the target viewport size for selecting video tracks. - * - * @param viewportWidth Viewport width in pixels. - * @param viewportHeight Viewport height in pixels. - * @param orientationMayChange Whether orientation may change during playback. - */ - public void setViewportSize(int viewportWidth, int viewportHeight, boolean orientationMayChange) { - if (this.viewportWidth != viewportWidth || this.viewportHeight != viewportHeight - || this.orientationMayChange != orientationMayChange) { - this.viewportWidth = viewportWidth; - this.viewportHeight = viewportHeight; - this.orientationMayChange = orientationMayChange; - invalidate(); - } - } - - /** - * Retrieves the viewport size from the provided {@link Context} and calls - * {@link #setViewportSize(int, int, boolean)} with this information. - * - * @param context The context to obtain the viewport size from. - * @param orientationMayChange Whether orientation may change during playback. - */ - public void setViewportSizeFromContext(Context context, boolean orientationMayChange) { - Point viewportSize = Util.getPhysicalDisplaySize(context); // Assume the viewport is fullscreen. - setViewportSize(viewportSize.x, viewportSize.y, orientationMayChange); - } - - /** - * Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. - */ - public void clearViewportConstraints() { - setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); + public Parameters getParameters() { + return params.get(); } // MappingTrackSelector implementation. @@ -228,22 +378,25 @@ public class DefaultTrackSelector extends MappingTrackSelector { throws ExoPlaybackException { // Make a track selection for each renderer. TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length]; + Parameters params = this.params.get(); for (int i = 0; i < rendererCapabilities.length; i++) { switch (rendererCapabilities[i].getTrackType()) { case C.TRACK_TYPE_VIDEO: rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], - rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight, - allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth, - viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory, - exceedVideoConstraintsIfNecessary); + rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth, + params.maxVideoHeight, params.allowNonSeamlessAdaptiveness, + params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight, + params.orientationMayChange, adaptiveVideoTrackSelectionFactory, + params.exceedVideoConstraintsIfNecessary); break; case C.TRACK_TYPE_AUDIO: rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], preferredAudioLanguage); + rendererFormatSupports[i], params.preferredAudioLanguage); break; case C.TRACK_TYPE_TEXT: rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], preferredTextLanguage, preferredAudioLanguage); + rendererFormatSupports[i], params.preferredTextLanguage, + params.preferredAudioLanguage); break; default: rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), @@ -442,7 +595,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { if (isSupported(trackFormatSupport[trackIndex])) { Format format = trackGroup.getFormat(trackIndex); - boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0; + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; int trackScore; if (formatHasLanguage(format, preferredAudioLanguage)) { if (isDefault) { @@ -480,8 +633,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { if (isSupported(trackFormatSupport[trackIndex])) { Format format = trackGroup.getFormat(trackIndex); - boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0; - boolean isForced = (format.selectionFlags & Format.SELECTION_FLAG_FORCED) != 0; + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0; int trackScore; if (formatHasLanguage(format, preferredTextLanguage)) { if (isDefault) { @@ -530,7 +683,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { if (isSupported(trackFormatSupport[trackIndex])) { Format format = trackGroup.getFormat(trackIndex); - boolean isDefault = (format.selectionFlags & Format.SELECTION_FLAG_DEFAULT) != 0; + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; int trackScore = isDefault ? 2 : 1; if (trackScore > selectedTrackScore) { selectedGroup = trackGroup; diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 5097079874..3826ee4668 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -16,39 +16,23 @@ package com.google.android.exoplayer2.trackselection; import android.os.Handler; -import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CopyOnWriteArraySet; /** * 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. */ -public abstract class MappingTrackSelector extends TrackSelector { - - /** - * 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); - - } +public abstract class MappingTrackSelector extends TrackSelector { /** * A track selection override. @@ -96,51 +80,19 @@ public abstract class MappingTrackSelector extends TrackSelector { } - private final Handler eventHandler; - private final CopyOnWriteArraySet listeners; private final SparseArray> selectionOverrides; private final SparseBooleanArray rendererDisabledFlags; - private TrackInfo activeTrackInfo; - /** * @param eventHandler A handler to use when delivering events to listeners added via * {@link #addListener(EventListener)}. */ public MappingTrackSelector(Handler eventHandler) { - this.eventHandler = eventHandler; - this.listeners = new CopyOnWriteArraySet<>(); + super(eventHandler); selectionOverrides = new SparseArray<>(); 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. * @@ -272,13 +224,7 @@ public abstract class MappingTrackSelector extends TrackSelector { // TrackSelector implementation. @Override - public final void onSelectionActivated(Object selectionInfo) { - activeTrackInfo = (TrackInfo) selectionInfo; - notifyTrackInfoChanged(activeTrackInfo); - } - - @Override - public final Pair selectTracks( + public final TrackSelections selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException { // 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 { } // Package up the track information and selections. - TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections); - TrackInfo trackInfo = new TrackInfo(rendererTrackTypes, rendererTrackGroupArrays, - trackSelections, mixedMimeTypeAdaptationSupport, rendererFormatSupports, + MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, + rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray); - return Pair.create(trackSelectionArray, trackInfo); + return new TrackSelections<>(mappedTrackInfo, trackSelections); } /** @@ -446,23 +391,10 @@ public abstract class MappingTrackSelector extends TrackSelector { 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. */ - public static final class TrackInfo { + public static final class MappedTrackInfo { /** * The renderer does not have any associated tracks. @@ -477,34 +409,27 @@ public abstract class MappingTrackSelector extends TrackSelector { */ public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 2; - /** - * The number of renderers. - */ - public final int rendererCount; - private final int[] rendererTrackTypes; private final TrackGroupArray[] trackGroups; - private final TrackSelection[] trackSelections; private final int[] mixedMimeTypeAdaptiveSupport; private final int[][][] formatSupport; private final TrackGroupArray unassociatedTrackGroups; + private final int rendererCount; /** * @param rendererTrackTypes The track type supported by 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 * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each * track, indexed by renderer index, group index and track index (in that order). * @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer. */ - /* package */ TrackInfo(int[] rendererTrackTypes, TrackGroupArray[] trackGroups, - TrackSelection[] trackSelections, int[] mixedMimeTypeAdaptiveSupport, + /* package */ MappedTrackInfo(int[] rendererTrackTypes, + TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport, int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { this.rendererTrackTypes = rendererTrackTypes; this.trackGroups = trackGroups; - this.trackSelections = trackSelections; this.formatSupport = formatSupport; this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; this.unassociatedTrackGroups = unassociatedTrackGroups; @@ -521,16 +446,6 @@ public abstract class MappingTrackSelector extends TrackSelector { return trackGroups[rendererIndex]; } - /** - * 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. * @@ -657,7 +572,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * unplayable. False in all other cases. */ 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++) { if (rendererTrackTypes[i] == trackType) { rendererSupport = Math.max(rendererSupport, getRendererSupport(i)); diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelections.java similarity index 80% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java rename to library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelections.java index 4d4b4c614e..31ed838310 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelections.java @@ -18,12 +18,16 @@ package com.google.android.exoplayer2.trackselection; 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 { /** - * 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; @@ -33,9 +37,11 @@ public final class TrackSelectionArray { private int hashCode; /** + * @param info Opaque information associated with the result. * @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.length = trackSelections.length; } @@ -75,7 +81,7 @@ public final class TrackSelectionArray { if (obj == null || getClass() != obj.getClass()) { return false; } - TrackSelectionArray other = (TrackSelectionArray) obj; + TrackSelections other = (TrackSelections) obj; return Arrays.equals(trackSelections, other.trackSelections); } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java index b18184ab3e..41c62f6e0e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java @@ -15,15 +15,15 @@ */ 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.RendererCapabilities; 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. - */ -public abstract class TrackSelector { +/** Selects tracks to be consumed by available renderers. */ +public abstract class TrackSelector { /** * Notified when previous selections by a {@link TrackSelector} are no longer valid. @@ -37,7 +37,55 @@ public abstract class TrackSelector { } + /** Listener of {@link TrackSelector} events. */ + public interface EventListener { + + /** + * Called when the track selections have changed. + * + * @param trackSelections The new track selections. + */ + void onTrackSelectionsChanged(TrackSelections trackSelections); + } + + private final Handler eventHandler; + private final CopyOnWriteArraySet> listeners; + private InvalidationListener listener; + private TrackSelections 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 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 listener) { + listeners.remove(listener); + } + + /** Returns the current track selections. */ + public final TrackSelections getCurrentSelections() { + return activeSelections; + } /** * Initializes the selector. @@ -49,31 +97,28 @@ public abstract class TrackSelector { } /** - * Generates a {@link TrackSelection} for each renderer. - *

    - * 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. + * Generates {@link TrackSelections} for the renderers. * - * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which {@link + * TrackSelection}s are to be generated. * @param trackGroups The available track groups. - * @return A {@link TrackSelectionArray} containing a {@link TrackSelection} for each renderer, - * together with an opaque object that will be passed to {@link #onSelectionActivated(Object)} - * if the selection is activated. + * @return The track selections. * @throws ExoPlaybackException If an error occurs selecting tracks. */ - public abstract Pair selectTracks( + public abstract TrackSelections selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException; /** - * Called when a selection previously generated by - * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} is activated. + * Called when {@link TrackSelections} previously generated by {@link + * #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 activeSelections) { + this.activeSelections = activeSelections; + notifyTrackSelectionsChanged(activeSelections); + } /** * Invalidates all previously generated track selections. @@ -84,4 +129,18 @@ public abstract class TrackSelector { } } + private void notifyTrackSelectionsChanged(final TrackSelections activeSelections) { + if (eventHandler != null) { + eventHandler.post( + new Runnable() { + @Override + public void run() { + for (EventListener listener : listeners) { + listener.onTrackSelectionsChanged(activeSelections); + } + } + }); + } + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 25a8e1e716..7f351afe8b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -108,7 +108,7 @@ public class PlaybackControlView extends FrameLayout { formatter = new Formatter(formatBuilder, Locale.getDefault()); 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); timeCurrent = (TextView) findViewById(R.id.time_current); progressBar = (SeekBar) findViewById(R.id.mediacontroller_progress); @@ -240,9 +240,11 @@ public class PlaybackControlView extends FrameLayout { return; } boolean playing = player != null && player.getPlayWhenReady(); - playButton.setImageResource(playing ? R.drawable.ic_media_pause : R.drawable.ic_media_play); - playButton.setContentDescription( - getResources().getString(playing ? R.string.pause_description : R.string.play_description)); + String contentDescription = getResources().getString( + playing ? R.string.exo_controls_pause_description : R.string.exo_controls_play_description); + playButton.setContentDescription(contentDescription); + playButton.setImageResource( + playing ? R.drawable.exo_controls_pause : R.drawable.exo_controls_play); } private void updateNavigation() { diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 390ab2ee7a..9263918958 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -22,22 +22,18 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.Surface; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; - import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.SimpleExoPlayer; 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.TextRenderer; - import java.util.List; /** @@ -71,8 +67,7 @@ public final class SimpleExoPlayerView extends FrameLayout { TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SimpleExoPlayerView, 0, 0); try { - useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, - useController); + useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController); useTextureView = a.getBoolean(R.styleable.SimpleExoPlayerView_use_texture_view, useTextureView); } finally { @@ -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(); layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame); controller = (PlaybackControlView) findViewById(R.id.control); @@ -113,7 +108,6 @@ public final class SimpleExoPlayerView extends FrameLayout { this.player.setVideoSurface(null); } this.player = player; - if (player != null) { if (surfaceView instanceof TextureView) { player.setVideoTextureView((TextureView) surfaceView); @@ -123,6 +117,8 @@ public final class SimpleExoPlayerView extends FrameLayout { player.setVideoListener(componentListener); player.addListener(componentListener); player.setTextOutput(componentListener); + } else { + shutterView.setVisibility(VISIBLE); } setUseController(useController); } @@ -234,12 +230,12 @@ public final class SimpleExoPlayerView extends FrameLayout { } @Override - public void onRenderedFirstFrame(Surface surface) { + public void onRenderedFirstFrame() { shutterView.setVisibility(GONE); } @Override - public void onVideoDisabled(DecoderCounters counters) { + public void onVideoTracksDisabled() { shutterView.setVisibility(VISIBLE); } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index cb4eec40f1..8c3ac77cb2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -66,9 +66,12 @@ import com.google.android.exoplayer2.util.Util; private CharSequence cueText; private Alignment cueTextAlignment; private float cueLine; + @Cue.LineType private int cueLineType; + @Cue.AnchorType private int cueLineAnchor; private float cuePosition; + @Cue.AnchorType private int cuePositionAnchor; private float cueSize; private boolean applyEmbeddedStyles; @@ -76,6 +79,7 @@ import com.google.android.exoplayer2.util.Util; private int backgroundColor; private int windowColor; private int edgeColor; + @CaptionStyleCompat.EdgeType private int edgeType; private float textSizePx; private float bottomPaddingFraction; diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index 5a469e1c90..d251446976 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -16,8 +16,11 @@ package com.google.android.exoplayer2.upstream; import android.net.Uri; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -25,6 +28,12 @@ import java.util.Arrays; */ 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. *

    @@ -69,6 +78,7 @@ public final class DataSpec { /** * Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag. */ + @Flags public final int flags; /** @@ -86,7 +96,7 @@ public final class DataSpec { * @param uri {@link #uri}. * @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); } @@ -111,7 +121,7 @@ public final class DataSpec { * @param key {@link #key}. * @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); } @@ -127,7 +137,7 @@ public final class DataSpec { * @param flags {@link #flags}. */ 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); } @@ -144,7 +154,7 @@ public final class DataSpec { * @param flags {@link #flags}. */ 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(position >= 0); Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET); diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index bcb4b9d2bd..e00f93aa19 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -547,11 +547,8 @@ public class DefaultHttpDataSource implements HttpDataSource { * @throws IOException If an error occurs reading from the source. */ 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) { - // We've read all of the requested data. - return C.RESULT_END_OF_INPUT; + return 0; } int read = inputStream.read(buffer, offset, readLength); diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index b96fe515fc..f915ee4e24 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.upstream; +import android.support.annotation.IntDef; import android.text.TextUtils; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; @@ -57,10 +60,14 @@ public interface HttpDataSource extends DataSource { */ 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_READ = 2; public static final int TYPE_CLOSE = 3; + @Type public final int type; /** @@ -68,25 +75,26 @@ public interface HttpDataSource extends DataSource { */ public final DataSpec dataSpec; - public HttpDataSourceException(DataSpec dataSpec, int type) { + public HttpDataSourceException(DataSpec dataSpec, @Type int type) { super(); this.dataSpec = dataSpec; this.type = type; } - public HttpDataSourceException(String message, DataSpec dataSpec, int type) { + public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) { super(message); this.dataSpec = dataSpec; this.type = type; } - public HttpDataSourceException(IOException cause, DataSpec dataSpec, int type) { + public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) { super(cause); this.dataSpec = dataSpec; 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); this.dataSpec = dataSpec; this.type = type; diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 0cd7d54564..af2555bfb8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.upstream.cache; import android.net.Uri; +import android.support.annotation.IntDef; import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSink; @@ -27,6 +28,8 @@ import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; import java.io.IOException; 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 @@ -43,6 +46,13 @@ public final class CacheDataSource implements DataSource { */ 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 * set, then we will read from upstream if the cache key is locked. @@ -106,7 +116,7 @@ public final class CacheDataSource implements DataSource { * 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}. */ - 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); } @@ -123,7 +133,8 @@ public final class CacheDataSource implements DataSource { * 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. */ - 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), flags, null); } @@ -142,7 +153,7 @@ public final class CacheDataSource implements DataSource { * @param eventListener An optional {@link EventListener} to receive events. */ 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.cacheReadDataSource = cacheReadDataSource; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index a678d9487b..561aba0146 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -15,6 +15,9 @@ */ package com.google.android.exoplayer2.util; +import android.text.TextUtils; +import com.google.android.exoplayer2.C; + /** * Defines common MIME types and helper methods. */ @@ -61,7 +64,7 @@ public final class MimeTypes { 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_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_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; @@ -191,6 +194,44 @@ public final class MimeTypes { return null; } + /** + * 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}. * diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index a499dc8012..b306fbf76e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -35,17 +35,17 @@ public final class 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) { - this.data = new byte[length]; - limit = data.length; + public ParsableByteArray(int limit) { + this.data = new byte[limit]; + 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. */ @@ -58,7 +58,7 @@ public final class ParsableByteArray { * Creates a new instance that wraps an existing array. * * @param data The data to wrap. - * @param limit The limit. + * @param limit The limit to set. */ public ParsableByteArray(byte[] data, int limit) { this.data = data; @@ -79,7 +79,7 @@ public final class ParsableByteArray { * Updates the instance to wrap {@code data}, and resets the position to zero. * * @param data The array to wrap. - * @param limit The limit. + * @param limit The limit to set. */ public void reset(byte[] data, int limit) { this.data = data; @@ -194,6 +194,13 @@ public final class ParsableByteArray { position += length; } + /** + * Peeks at the next byte as an unsigned value. + */ + public int peekUnsignedByte() { + return (data[position] & 0xFF); + } + /** * Reads the next byte as an unsigned value. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/src/main/java/com/google/android/exoplayer2/util/Util.java index 6e11c43db1..c4505fd8b9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -88,27 +88,6 @@ public final class Util { */ 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 Pattern XS_DATE_TIME_PATTERN = Pattern.compile( "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" @@ -712,6 +691,7 @@ public final class Util { * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then * {@link C#ENCODING_INVALID} is returned. */ + @C.PcmEncoding public static int getPcmEncoding(int bitDepth) { switch (bitDepth) { case 8: @@ -731,19 +711,20 @@ public final class Util { * 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. - * @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) { if (fileName == null) { - return TYPE_OTHER; + return C.TYPE_OTHER; } else if (fileName.endsWith(".mpd")) { - return TYPE_DASH; + return C.TYPE_DASH; } else if (fileName.endsWith(".ism") || fileName.endsWith(".isml")) { - return TYPE_SS; + return C.TYPE_SS; } else if (fileName.endsWith(".m3u8")) { - return TYPE_HLS; + return C.TYPE_HLS; } else { - return TYPE_OTHER; + return C.TYPE_OTHER; } } diff --git a/library/src/main/res/drawable-hdpi/ic_media_ff.png b/library/src/main/res/drawable-hdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_ff.png rename to library/src/main/res/drawable-hdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-hdpi/ic_media_next.png b/library/src/main/res/drawable-hdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_next.png rename to library/src/main/res/drawable-hdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-hdpi/ic_media_pause.png b/library/src/main/res/drawable-hdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_pause.png rename to library/src/main/res/drawable-hdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-hdpi/ic_media_play.png b/library/src/main/res/drawable-hdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_play.png rename to library/src/main/res/drawable-hdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-hdpi/ic_media_previous.png b/library/src/main/res/drawable-hdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_previous.png rename to library/src/main/res/drawable-hdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-hdpi/ic_media_rew.png b/library/src/main/res/drawable-hdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-hdpi/ic_media_rew.png rename to library/src/main/res/drawable-hdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_ff.png b/library/src/main/res/drawable-ldpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_ff.png rename to library/src/main/res/drawable-ldpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_next.png b/library/src/main/res/drawable-ldpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_next.png rename to library/src/main/res/drawable-ldpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_pause.png b/library/src/main/res/drawable-ldpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_pause.png rename to library/src/main/res/drawable-ldpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_play.png b/library/src/main/res/drawable-ldpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_play.png rename to library/src/main/res/drawable-ldpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_previous.png b/library/src/main/res/drawable-ldpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_previous.png rename to library/src/main/res/drawable-ldpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-ldpi/ic_media_rew.png b/library/src/main/res/drawable-ldpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-ldpi/ic_media_rew.png rename to library/src/main/res/drawable-ldpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_ff.png b/library/src/main/res/drawable-mdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_ff.png rename to library/src/main/res/drawable-mdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_next.png b/library/src/main/res/drawable-mdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_next.png rename to library/src/main/res/drawable-mdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_pause.png b/library/src/main/res/drawable-mdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_pause.png rename to library/src/main/res/drawable-mdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_play.png b/library/src/main/res/drawable-mdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_play.png rename to library/src/main/res/drawable-mdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_previous.png b/library/src/main/res/drawable-mdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_previous.png rename to library/src/main/res/drawable-mdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-mdpi/ic_media_rew.png b/library/src/main/res/drawable-mdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-mdpi/ic_media_rew.png rename to library/src/main/res/drawable-mdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_ff.png b/library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_ff.png rename to library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_next.png b/library/src/main/res/drawable-xhdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_next.png rename to library/src/main/res/drawable-xhdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_pause.png b/library/src/main/res/drawable-xhdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_pause.png rename to library/src/main/res/drawable-xhdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_play.png b/library/src/main/res/drawable-xhdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_play.png rename to library/src/main/res/drawable-xhdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_previous.png b/library/src/main/res/drawable-xhdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_previous.png rename to library/src/main/res/drawable-xhdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-xhdpi/ic_media_rew.png b/library/src/main/res/drawable-xhdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/ic_media_rew.png rename to library/src/main/res/drawable-xhdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_ff.png b/library/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_ff.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_next.png b/library/src/main/res/drawable-xxhdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_next.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_pause.png b/library/src/main/res/drawable-xxhdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_pause.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_play.png b/library/src/main/res/drawable-xxhdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_play.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_previous.png b/library/src/main/res/drawable-xxhdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_previous.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-xxhdpi/ic_media_rew.png b/library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/ic_media_rew.png rename to library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png diff --git a/library/src/main/res/layout/playback_control_view.xml b/library/src/main/res/layout/exo_playback_control_view.xml similarity index 82% rename from library/src/main/res/layout/playback_control_view.xml rename to library/src/main/res/layout/exo_playback_control_view.xml index 21194ef54f..a0be4a8149 100644 --- a/library/src/main/res/layout/playback_control_view.xml +++ b/library/src/main/res/layout/exo_playback_control_view.xml @@ -29,23 +29,23 @@ android:orientation="horizontal"> + android:contentDescription="@string/exo_controls_previous_description" + style="@style/ExoMediaButton.Previous"/> + android:contentDescription="@string/exo_controls_rewind_description" + style="@style/ExoMediaButton.Rewind"/> + style="@style/ExoMediaButton"/> + android:contentDescription="@string/exo_controls_fastforward_description" + style="@style/ExoMediaButton.FastForward"/> + android:contentDescription="@string/exo_controls_previous_description" + style="@style/ExoMediaButton.Next"/> diff --git a/library/src/main/res/layout/exoplayer_video_view.xml b/library/src/main/res/layout/exo_simple_player_view.xml similarity index 100% rename from library/src/main/res/layout/exoplayer_video_view.xml rename to library/src/main/res/layout/exo_simple_player_view.xml diff --git a/library/src/main/res/values-af/strings.xml b/library/src/main/res/values-af/strings.xml index 2ce5c415de..9f1bce53d9 100644 --- a/library/src/main/res/values-af/strings.xml +++ b/library/src/main/res/values-af/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Vorige snit" - "Volgende snit" - "Wag" - "Speel" - "Stop" - "Spoel terug" - "Vinnig vorentoe" + "Vorige snit" + "Volgende snit" + "Wag" + "Speel" + "Stop" + "Spoel terug" + "Vinnig vorentoe" diff --git a/library/src/main/res/values-am/strings.xml b/library/src/main/res/values-am/strings.xml index 39591a86f3..f06c2a664e 100644 --- a/library/src/main/res/values-am/strings.xml +++ b/library/src/main/res/values-am/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "ቀዳሚ ትራክ" - "ቀጣይ ትራክ" - "ለአፍታ አቁም" - "አጫውት" - "አቁም" - "ወደኋላ አጠንጥን" - "በፍጥነት አሳልፍ" + "ቀዳሚ ትራክ" + "ቀጣይ ትራክ" + "ለአፍታ አቁም" + "አጫውት" + "አቁም" + "ወደኋላ አጠንጥን" + "በፍጥነት አሳልፍ" diff --git a/library/src/main/res/values-ar/strings.xml b/library/src/main/res/values-ar/strings.xml index 96b7772ba1..a40c961bf7 100644 --- a/library/src/main/res/values-ar/strings.xml +++ b/library/src/main/res/values-ar/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "المقطع الصوتي السابق" - "المقطع الصوتي التالي" - "إيقاف مؤقت" - "تشغيل" - "إيقاف" - "إرجاع" - "تقديم سريع" + "المقطع الصوتي السابق" + "المقطع الصوتي التالي" + "إيقاف مؤقت" + "تشغيل" + "إيقاف" + "إرجاع" + "تقديم سريع" diff --git a/library/src/main/res/values-az-rAZ/strings.xml b/library/src/main/res/values-az-rAZ/strings.xml index aa85203b4d..7b3b9366b5 100644 --- a/library/src/main/res/values-az-rAZ/strings.xml +++ b/library/src/main/res/values-az-rAZ/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Öncəki trek" - "Növbəti trek" - "Pauza" - "Oyun" - "Dayandır" - "Geri sarıma" - "Sürətlə irəli" + "Öncəki trek" + "Növbəti trek" + "Pauza" + "Oyun" + "Dayandır" + "Geri sarıma" + "Sürətlə irəli" diff --git a/library/src/main/res/values-b+sr+Latn/strings.xml b/library/src/main/res/values-b+sr+Latn/strings.xml index 5798cfb96d..b5fdd74402 100644 --- a/library/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/src/main/res/values-b+sr+Latn/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Prethodna pesma" - "Sledeća pesma" - "Pauza" - "Pusti" - "Zaustavi" - "Premotaj unazad" - "Premotaj unapred" + "Prethodna pesma" + "Sledeća pesma" + "Pauza" + "Pusti" + "Zaustavi" + "Premotaj unazad" + "Premotaj unapred" diff --git a/library/src/main/res/values-be-rBY/strings.xml b/library/src/main/res/values-be-rBY/strings.xml index 7c77eef2f2..890c23ebd5 100644 --- a/library/src/main/res/values-be-rBY/strings.xml +++ b/library/src/main/res/values-be-rBY/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Папярэдні трэк" - "Наступны трэк" - "Прыпыніць" - "Прайграць" - "Спыніць" - "Перамотка назад" - "Перамотка ўперад" + "Папярэдні трэк" + "Наступны трэк" + "Прыпыніць" + "Прайграць" + "Спыніць" + "Перамотка назад" + "Перамотка ўперад" diff --git a/library/src/main/res/values-bg/strings.xml b/library/src/main/res/values-bg/strings.xml index 2f771368c4..30b905fb8e 100644 --- a/library/src/main/res/values-bg/strings.xml +++ b/library/src/main/res/values-bg/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Предишен запис" - "Следващ запис" - "Пауза" - "Пускане" - "Спиране" - "Превъртане назад" - "Превъртане напред" + "Предишен запис" + "Следващ запис" + "Пауза" + "Пускане" + "Спиране" + "Превъртане назад" + "Превъртане напред" diff --git a/library/src/main/res/values-bn-rBD/strings.xml b/library/src/main/res/values-bn-rBD/strings.xml index 7140ab7f8d..ca5d9461d3 100644 --- a/library/src/main/res/values-bn-rBD/strings.xml +++ b/library/src/main/res/values-bn-rBD/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "পূর্ববর্তী ট্র্যাক" - "পরবর্তী ট্র্যাক" - "বিরাম দিন" - "প্লে করুন" - "থামান" - "গুটিয়ে নিন" - "দ্রুত সামনে এগোন" + "পূর্ববর্তী ট্র্যাক" + "পরবর্তী ট্র্যাক" + "বিরাম দিন" + "প্লে করুন" + "থামান" + "গুটিয়ে নিন" + "দ্রুত সামনে এগোন" diff --git a/library/src/main/res/values-bs-rBA/strings.xml b/library/src/main/res/values-bs-rBA/strings.xml index 0f578946e6..9cb0ca4d76 100644 --- a/library/src/main/res/values-bs-rBA/strings.xml +++ b/library/src/main/res/values-bs-rBA/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Prethodna numera" - "Sljedeća numera" - "Pauziraj" - "Reproduciraj" - "Zaustavi" - "Premotaj" - "Ubrzaj" + "Prethodna numera" + "Sljedeća numera" + "Pauziraj" + "Reproduciraj" + "Zaustavi" + "Premotaj" + "Ubrzaj" diff --git a/library/src/main/res/values-ca/strings.xml b/library/src/main/res/values-ca/strings.xml index 03ea1f0790..0816c76b12 100644 --- a/library/src/main/res/values-ca/strings.xml +++ b/library/src/main/res/values-ca/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Ruta anterior" - "Ruta següent" - "Posa en pausa" - "Reprodueix" - "Atura" - "Rebobina" - "Avança ràpidament" + "Ruta anterior" + "Ruta següent" + "Posa en pausa" + "Reprodueix" + "Atura" + "Rebobina" + "Avança ràpidament" diff --git a/library/src/main/res/values-cs/strings.xml b/library/src/main/res/values-cs/strings.xml index eb28c8d756..22cff4041e 100644 --- a/library/src/main/res/values-cs/strings.xml +++ b/library/src/main/res/values-cs/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Předchozí skladba" - "Další skladba" - "Pozastavit" - "Přehrát" - "Zastavit" - "Přetočit zpět" - "Přetočit vpřed" + "Předchozí skladba" + "Další skladba" + "Pozastavit" + "Přehrát" + "Zastavit" + "Přetočit zpět" + "Přetočit vpřed" diff --git a/library/src/main/res/values-da/strings.xml b/library/src/main/res/values-da/strings.xml index 04d64f9632..a6710bea50 100644 --- a/library/src/main/res/values-da/strings.xml +++ b/library/src/main/res/values-da/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Forrige nummer" - "Næste nummer" - "Pause" - "Afspil" - "Stop" - "Spol tilbage" - "Spol frem" + "Forrige nummer" + "Næste nummer" + "Pause" + "Afspil" + "Stop" + "Spol tilbage" + "Spol frem" diff --git a/library/src/main/res/values-de/strings.xml b/library/src/main/res/values-de/strings.xml index 63ebd19301..cdfd2d4baf 100644 --- a/library/src/main/res/values-de/strings.xml +++ b/library/src/main/res/values-de/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Vorheriger Titel" - "Nächster Titel" - "Pausieren" - "Wiedergabe" - "Beenden" - "Zurückspulen" - "Vorspulen" + "Vorheriger Titel" + "Nächster Titel" + "Pausieren" + "Wiedergabe" + "Beenden" + "Zurückspulen" + "Vorspulen" diff --git a/library/src/main/res/values-el/strings.xml b/library/src/main/res/values-el/strings.xml index f1744519c4..1e11df3b14 100644 --- a/library/src/main/res/values-el/strings.xml +++ b/library/src/main/res/values-el/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Προηγούμενο κομμάτι" - "Επόμενο κομμάτι" - "Παύση" - "Αναπαραγωγή" - "Διακοπή" - "Επαναφορά" - "Γρήγορη προώθηση" + "Προηγούμενο κομμάτι" + "Επόμενο κομμάτι" + "Παύση" + "Αναπαραγωγή" + "Διακοπή" + "Επαναφορά" + "Γρήγορη προώθηση" diff --git a/library/src/main/res/values-en-rAU/strings.xml b/library/src/main/res/values-en-rAU/strings.xml index a89377fc1d..5077cf2b94 100644 --- a/library/src/main/res/values-en-rAU/strings.xml +++ b/library/src/main/res/values-en-rAU/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" diff --git a/library/src/main/res/values-en-rGB/strings.xml b/library/src/main/res/values-en-rGB/strings.xml index a89377fc1d..5077cf2b94 100644 --- a/library/src/main/res/values-en-rGB/strings.xml +++ b/library/src/main/res/values-en-rGB/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" diff --git a/library/src/main/res/values-en-rIN/strings.xml b/library/src/main/res/values-en-rIN/strings.xml index a89377fc1d..5077cf2b94 100644 --- a/library/src/main/res/values-en-rIN/strings.xml +++ b/library/src/main/res/values-en-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" diff --git a/library/src/main/res/values-es-rUS/strings.xml b/library/src/main/res/values-es-rUS/strings.xml index 6e1dcf01de..72b176e538 100644 --- a/library/src/main/res/values-es-rUS/strings.xml +++ b/library/src/main/res/values-es-rUS/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Pista anterior" - "Siguiente pista" - "Pausar" - "Reproducir" - "Detener" - "Retroceder" - "Avanzar" + "Pista anterior" + "Siguiente pista" + "Pausar" + "Reproducir" + "Detener" + "Retroceder" + "Avanzar" diff --git a/library/src/main/res/values-es/strings.xml b/library/src/main/res/values-es/strings.xml index 4f108bc5df..3b188d266d 100644 --- a/library/src/main/res/values-es/strings.xml +++ b/library/src/main/res/values-es/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Canción anterior" - "Siguiente canción" - "Pausar" - "Reproducir" - "Detener" - "Rebobinar" - "Avance rápido" + "Canción anterior" + "Siguiente canción" + "Pausar" + "Reproducir" + "Detener" + "Rebobinar" + "Avance rápido" diff --git a/library/src/main/res/values-et-rEE/strings.xml b/library/src/main/res/values-et-rEE/strings.xml index 588510dc32..7a01bd9d5a 100644 --- a/library/src/main/res/values-et-rEE/strings.xml +++ b/library/src/main/res/values-et-rEE/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Eelmine lugu" - "Järgmine lugu" - "Peata" - "Esita" - "Peata" - "Keri tagasi" - "Keri edasi" + "Eelmine lugu" + "Järgmine lugu" + "Peata" + "Esita" + "Peata" + "Keri tagasi" + "Keri edasi" diff --git a/library/src/main/res/values-eu-rES/strings.xml b/library/src/main/res/values-eu-rES/strings.xml index fa96388522..3dd51d2138 100644 --- a/library/src/main/res/values-eu-rES/strings.xml +++ b/library/src/main/res/values-eu-rES/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Aurreko pista" - "Hurrengo pista" - "Pausatu" - "Erreproduzitu" - "Gelditu" - "Atzeratu" - "Aurreratu" + "Aurreko pista" + "Hurrengo pista" + "Pausatu" + "Erreproduzitu" + "Gelditu" + "Atzeratu" + "Aurreratu" diff --git a/library/src/main/res/values-fa/strings.xml b/library/src/main/res/values-fa/strings.xml index 84c99cae18..a8955ca2f3 100644 --- a/library/src/main/res/values-fa/strings.xml +++ b/library/src/main/res/values-fa/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "آهنگ قبلی" - "آهنگ بعدی" - "مکث" - "پخش" - "توقف" - "عقب بردن" - "جلو بردن سریع" + "آهنگ قبلی" + "آهنگ بعدی" + "مکث" + "پخش" + "توقف" + "عقب بردن" + "جلو بردن سریع" diff --git a/library/src/main/res/values-fi/strings.xml b/library/src/main/res/values-fi/strings.xml index ae0e25b1aa..5f1352d1af 100644 --- a/library/src/main/res/values-fi/strings.xml +++ b/library/src/main/res/values-fi/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Edellinen raita" - "Seuraava raita" - "Tauko" - "Toista" - "Seis" - "Kelaa taakse" - "Kelaa eteen" + "Edellinen raita" + "Seuraava raita" + "Tauko" + "Toista" + "Seis" + "Kelaa taakse" + "Kelaa eteen" diff --git a/library/src/main/res/values-fr-rCA/strings.xml b/library/src/main/res/values-fr-rCA/strings.xml index 5215b00d82..51ba11e0c0 100644 --- a/library/src/main/res/values-fr-rCA/strings.xml +++ b/library/src/main/res/values-fr-rCA/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Chanson précédente" - "Chanson suivante" - "Pause" - "Lecture" - "Arrêter" - "Reculer" - "Avance rapide" + "Chanson précédente" + "Chanson suivante" + "Pause" + "Lecture" + "Arrêter" + "Reculer" + "Avance rapide" diff --git a/library/src/main/res/values-fr/strings.xml b/library/src/main/res/values-fr/strings.xml index 7980c80b47..d55b32b6f7 100644 --- a/library/src/main/res/values-fr/strings.xml +++ b/library/src/main/res/values-fr/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Piste précédente" - "Piste suivante" - "Interrompre" - "Lire" - "Arrêter" - "Retour arrière" - "Avance rapide" + "Piste précédente" + "Piste suivante" + "Interrompre" + "Lire" + "Arrêter" + "Retour arrière" + "Avance rapide" diff --git a/library/src/main/res/values-gl-rES/strings.xml b/library/src/main/res/values-gl-rES/strings.xml index 05864b2633..99ae59c7f9 100644 --- a/library/src/main/res/values-gl-rES/strings.xml +++ b/library/src/main/res/values-gl-rES/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Pista anterior" - "Seguinte pista" - "Pausar" - "Reproducir" - "Deter" - "Rebobinar" - "Avance rápido" + "Pista anterior" + "Seguinte pista" + "Pausar" + "Reproducir" + "Deter" + "Rebobinar" + "Avance rápido" diff --git a/library/src/main/res/values-gu-rIN/strings.xml b/library/src/main/res/values-gu-rIN/strings.xml index b935154cc4..6feab0a3a6 100644 --- a/library/src/main/res/values-gu-rIN/strings.xml +++ b/library/src/main/res/values-gu-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "પહેલાનો ટ્રૅક" - "આગલો ટ્રૅક" - "થોભો" - "ચલાવો" - "રોકો" - "રીવાઇન્ડ કરો" - "ઝડપી ફોરવર્ડ કરો" + "પહેલાનો ટ્રૅક" + "આગલો ટ્રૅક" + "થોભો" + "ચલાવો" + "રોકો" + "રીવાઇન્ડ કરો" + "ઝડપી ફોરવર્ડ કરો" diff --git a/library/src/main/res/values-hi/strings.xml b/library/src/main/res/values-hi/strings.xml index 08415bc992..5229b67d0e 100644 --- a/library/src/main/res/values-hi/strings.xml +++ b/library/src/main/res/values-hi/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "पिछला ट्रैक" - "अगला ट्रैक" - "रोकें" - "चलाएं" - "बंद करें" - "रिवाइंड करें" - "फ़ास्ट फ़ॉरवर्ड" + "पिछला ट्रैक" + "अगला ट्रैक" + "रोकें" + "चलाएं" + "बंद करें" + "रिवाइंड करें" + "फ़ास्ट फ़ॉरवर्ड" diff --git a/library/src/main/res/values-hr/strings.xml b/library/src/main/res/values-hr/strings.xml index 6fe1ba9288..c0b075edde 100644 --- a/library/src/main/res/values-hr/strings.xml +++ b/library/src/main/res/values-hr/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Prethodna pjesma" - "Sljedeća pjesma" - "Pauziraj" - "Reproduciraj" - "Zaustavi" - "Unatrag" - "Brzo unaprijed" + "Prethodna pjesma" + "Sljedeća pjesma" + "Pauziraj" + "Reproduciraj" + "Zaustavi" + "Unatrag" + "Brzo unaprijed" diff --git a/library/src/main/res/values-hu/strings.xml b/library/src/main/res/values-hu/strings.xml index be28793dc3..2a34684edb 100644 --- a/library/src/main/res/values-hu/strings.xml +++ b/library/src/main/res/values-hu/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Előző szám" - "Következő szám" - "Szünet" - "Lejátszás" - "Leállítás" - "Visszatekerés" - "Előretekerés" + "Előző szám" + "Következő szám" + "Szünet" + "Lejátszás" + "Leállítás" + "Visszatekerés" + "Előretekerés" diff --git a/library/src/main/res/values-hy-rAM/strings.xml b/library/src/main/res/values-hy-rAM/strings.xml index 3052d6c26b..05f9d04ab7 100644 --- a/library/src/main/res/values-hy-rAM/strings.xml +++ b/library/src/main/res/values-hy-rAM/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Նախորդը" - "Հաջորդը" - "Դադարեցնել" - "Նվագարկել" - "Դադարեցնել" - "Հետ փաթաթել" - "Արագ առաջ անցնել" + "Նախորդը" + "Հաջորդը" + "Դադարեցնել" + "Նվագարկել" + "Դադարեցնել" + "Հետ փաթաթել" + "Արագ առաջ անցնել" diff --git a/library/src/main/res/values-in/strings.xml b/library/src/main/res/values-in/strings.xml index 6be2c2155c..062933a0a8 100644 --- a/library/src/main/res/values-in/strings.xml +++ b/library/src/main/res/values-in/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Lagu sebelumnya" - "Lagu berikutnya" - "Jeda" - "Putar" - "Berhenti" - "Putar Ulang" - "Maju cepat" + "Lagu sebelumnya" + "Lagu berikutnya" + "Jeda" + "Putar" + "Berhenti" + "Putar Ulang" + "Maju cepat" diff --git a/library/src/main/res/values-is-rIS/strings.xml b/library/src/main/res/values-is-rIS/strings.xml index 80b81fa7fb..9c4421a272 100644 --- a/library/src/main/res/values-is-rIS/strings.xml +++ b/library/src/main/res/values-is-rIS/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Fyrra lag" - "Næsta lag" - "Hlé" - "Spila" - "Stöðva" - "Spóla til baka" - "Spóla áfram" + "Fyrra lag" + "Næsta lag" + "Hlé" + "Spila" + "Stöðva" + "Spóla til baka" + "Spóla áfram" diff --git a/library/src/main/res/values-it/strings.xml b/library/src/main/res/values-it/strings.xml index 4d84822b8e..71525a2b3e 100644 --- a/library/src/main/res/values-it/strings.xml +++ b/library/src/main/res/values-it/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Traccia precedente" - "Traccia successiva" - "Metti in pausa" - "Riproduci" - "Interrompi" - "Riavvolgi" - "Avanti veloce" + "Traccia precedente" + "Traccia successiva" + "Metti in pausa" + "Riproduci" + "Interrompi" + "Riavvolgi" + "Avanti veloce" diff --git a/library/src/main/res/values-iw/strings.xml b/library/src/main/res/values-iw/strings.xml index 5449bb1a7c..f33cc2adb0 100644 --- a/library/src/main/res/values-iw/strings.xml +++ b/library/src/main/res/values-iw/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "הרצועה הקודמת" - "הרצועה הבאה" - "השהה" - "הפעל" - "הפסק" - "הרץ אחורה" - "הרץ קדימה" + "הרצועה הקודמת" + "הרצועה הבאה" + "השהה" + "הפעל" + "הפסק" + "הרץ אחורה" + "הרץ קדימה" diff --git a/library/src/main/res/values-ja/strings.xml b/library/src/main/res/values-ja/strings.xml index e745b18ee0..baa459aeca 100644 --- a/library/src/main/res/values-ja/strings.xml +++ b/library/src/main/res/values-ja/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "前のトラック" - "次のトラック" - "一時停止" - "再生" - "停止" - "巻き戻し" - "早送り" + "前のトラック" + "次のトラック" + "一時停止" + "再生" + "停止" + "巻き戻し" + "早送り" diff --git a/library/src/main/res/values-ka-rGE/strings.xml b/library/src/main/res/values-ka-rGE/strings.xml index 652186360c..5b87f86c34 100644 --- a/library/src/main/res/values-ka-rGE/strings.xml +++ b/library/src/main/res/values-ka-rGE/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "წინა ჩანაწერი" - "შემდეგი ჩანაწერი" - "პაუზა" - "დაკვრა" - "შეწყვეტა" - "უკან გადახვევა" - "წინ გადახვევა" + "წინა ჩანაწერი" + "შემდეგი ჩანაწერი" + "პაუზა" + "დაკვრა" + "შეწყვეტა" + "უკან გადახვევა" + "წინ გადახვევა" diff --git a/library/src/main/res/values-kk-rKZ/strings.xml b/library/src/main/res/values-kk-rKZ/strings.xml index eff4c334e0..c1bf5c8b4b 100644 --- a/library/src/main/res/values-kk-rKZ/strings.xml +++ b/library/src/main/res/values-kk-rKZ/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Алдыңғы трек" - "Келесі трек" - "Кідірту" - "Ойнату" - "Тоқтату" - "Кері айналдыру" - "Жылдам алға айналдыру" + "Алдыңғы трек" + "Келесі трек" + "Кідірту" + "Ойнату" + "Тоқтату" + "Кері айналдыру" + "Жылдам алға айналдыру" diff --git a/library/src/main/res/values-km-rKH/strings.xml b/library/src/main/res/values-km-rKH/strings.xml index 8298655078..dbeeab60a6 100644 --- a/library/src/main/res/values-km-rKH/strings.xml +++ b/library/src/main/res/values-km-rKH/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "បទ​មុន" - "បទ​បន្ទាប់" - "ផ្អាក" - "ចាក់" - "បញ្ឈប់" - "ខា​ថយក្រោយ" - "ទៅ​មុខ​​​រហ័ស" + "បទ​មុន" + "បទ​បន្ទាប់" + "ផ្អាក" + "ចាក់" + "បញ្ឈប់" + "ខា​ថយក្រោយ" + "ទៅ​មុខ​​​រហ័ស" diff --git a/library/src/main/res/values-kn-rIN/strings.xml b/library/src/main/res/values-kn-rIN/strings.xml index ac210680d8..b73cf0fdb0 100644 --- a/library/src/main/res/values-kn-rIN/strings.xml +++ b/library/src/main/res/values-kn-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್" - "ಮುಂದಿನ ಟ್ರ್ಯಾಕ್" - "ವಿರಾಮಗೊಳಿಸು" - "ಪ್ಲೇ ಮಾಡು" - "ನಿಲ್ಲಿಸು" - "ರಿವೈಂಡ್ ಮಾಡು" - "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" + "ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್" + "ಮುಂದಿನ ಟ್ರ್ಯಾಕ್" + "ವಿರಾಮಗೊಳಿಸು" + "ಪ್ಲೇ ಮಾಡು" + "ನಿಲ್ಲಿಸು" + "ರಿವೈಂಡ್ ಮಾಡು" + "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" diff --git a/library/src/main/res/values-ko/strings.xml b/library/src/main/res/values-ko/strings.xml index 38f9b7aae9..7097e2d9f7 100644 --- a/library/src/main/res/values-ko/strings.xml +++ b/library/src/main/res/values-ko/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "이전 트랙" - "다음 트랙" - "일시중지" - "재생" - "중지" - "되감기" - "빨리 감기" + "이전 트랙" + "다음 트랙" + "일시중지" + "재생" + "중지" + "되감기" + "빨리 감기" diff --git a/library/src/main/res/values-ky-rKG/strings.xml b/library/src/main/res/values-ky-rKG/strings.xml index 43591d760c..7090c178c3 100644 --- a/library/src/main/res/values-ky-rKG/strings.xml +++ b/library/src/main/res/values-ky-rKG/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Мурунку трек" - "Кийинки трек" - "Тындыруу" - "Ойнотуу" - "Токтотуу" - "Артка түрүү" - "Алдыга түрүү" + "Мурунку трек" + "Кийинки трек" + "Тындыруу" + "Ойнотуу" + "Токтотуу" + "Артка түрүү" + "Алдыга түрүү" diff --git a/library/src/main/res/values-lo-rLA/strings.xml b/library/src/main/res/values-lo-rLA/strings.xml index 885005406d..44095e4323 100644 --- a/library/src/main/res/values-lo-rLA/strings.xml +++ b/library/src/main/res/values-lo-rLA/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "​ເພງ​ກ່ອນ​ໜ້າ" - "​ເພງ​ຕໍ່​ໄປ" - "ຢຸດຊົ່ວຄາວ" - "ຫຼິ້ນ" - "ຢຸດ" - "​ຣີ​​ວາຍກັບ" - "ເລື່ອນ​ໄປ​ໜ້າ" + "​ເພງ​ກ່ອນ​ໜ້າ" + "​ເພງ​ຕໍ່​ໄປ" + "ຢຸດຊົ່ວຄາວ" + "ຫຼິ້ນ" + "ຢຸດ" + "​ຣີ​​ວາຍກັບ" + "ເລື່ອນ​ໄປ​ໜ້າ" diff --git a/library/src/main/res/values-lt/strings.xml b/library/src/main/res/values-lt/strings.xml index f828226100..138caec322 100644 --- a/library/src/main/res/values-lt/strings.xml +++ b/library/src/main/res/values-lt/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Ankstesnis takelis" - "Kitas takelis" - "Pristabdyti" - "Leisti" - "Stabdyti" - "Sukti atgal" - "Sukti pirmyn" + "Ankstesnis takelis" + "Kitas takelis" + "Pristabdyti" + "Leisti" + "Stabdyti" + "Sukti atgal" + "Sukti pirmyn" diff --git a/library/src/main/res/values-lv/strings.xml b/library/src/main/res/values-lv/strings.xml index b5f8f00e84..4c91da86cc 100644 --- a/library/src/main/res/values-lv/strings.xml +++ b/library/src/main/res/values-lv/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Iepriekšējais ieraksts" - "Nākamais ieraksts" - "Pārtraukt" - "Atskaņot" - "Apturēt" - "Attīt atpakaļ" - "Ātri patīt" + "Iepriekšējais ieraksts" + "Nākamais ieraksts" + "Pārtraukt" + "Atskaņot" + "Apturēt" + "Attīt atpakaļ" + "Ātri patīt" diff --git a/library/src/main/res/values-mk-rMK/strings.xml b/library/src/main/res/values-mk-rMK/strings.xml index aa96af37a1..e9fedf689f 100644 --- a/library/src/main/res/values-mk-rMK/strings.xml +++ b/library/src/main/res/values-mk-rMK/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Претходна песна" - "Следна песна" - "Пауза" - "Пушти" - "Запри" - "Премотај назад" - "Брзо премотај напред" + "Претходна песна" + "Следна песна" + "Пауза" + "Пушти" + "Запри" + "Премотај назад" + "Брзо премотај напред" diff --git a/library/src/main/res/values-ml-rIN/strings.xml b/library/src/main/res/values-ml-rIN/strings.xml index f59e90d24a..acc33934fb 100644 --- a/library/src/main/res/values-ml-rIN/strings.xml +++ b/library/src/main/res/values-ml-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "മുമ്പത്തെ ട്രാക്ക്" - "അടുത്ത ട്രാക്ക്" - "താൽക്കാലികമായി നിർത്തുക" - "പ്ലേ ചെയ്യുക" - "നിര്‍ത്തുക" - "റിവൈൻഡുചെയ്യുക" - "വേഗത്തിലുള്ള കൈമാറൽ" + "മുമ്പത്തെ ട്രാക്ക്" + "അടുത്ത ട്രാക്ക്" + "താൽക്കാലികമായി നിർത്തുക" + "പ്ലേ ചെയ്യുക" + "നിര്‍ത്തുക" + "റിവൈൻഡുചെയ്യുക" + "വേഗത്തിലുള്ള കൈമാറൽ" diff --git a/library/src/main/res/values-mn-rMN/strings.xml b/library/src/main/res/values-mn-rMN/strings.xml index 2ab27a803e..6434e9ea16 100644 --- a/library/src/main/res/values-mn-rMN/strings.xml +++ b/library/src/main/res/values-mn-rMN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Өмнөх трек" - "Дараагийн трек" - "Түр зогсоох" - "Тоглуулах" - "Зогсоох" - "Буцааж хураах" - "Хурдан урагшлуулах" + "Өмнөх трек" + "Дараагийн трек" + "Түр зогсоох" + "Тоглуулах" + "Зогсоох" + "Буцааж хураах" + "Хурдан урагшлуулах" diff --git a/library/src/main/res/values-mr-rIN/strings.xml b/library/src/main/res/values-mr-rIN/strings.xml index 827259e68b..8f4d0d75b1 100644 --- a/library/src/main/res/values-mr-rIN/strings.xml +++ b/library/src/main/res/values-mr-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "मागील ट्रॅक" - "पुढील ट्रॅक" - "विराम द्या" - "प्ले करा" - "थांबा" - "रिवाईँड करा" - "फास्ट फॉरवर्ड करा" + "मागील ट्रॅक" + "पुढील ट्रॅक" + "विराम द्या" + "प्ले करा" + "थांबा" + "रिवाईँड करा" + "फास्ट फॉरवर्ड करा" diff --git a/library/src/main/res/values-ms-rMY/strings.xml b/library/src/main/res/values-ms-rMY/strings.xml index e8bf6ec693..91f74bbc1c 100644 --- a/library/src/main/res/values-ms-rMY/strings.xml +++ b/library/src/main/res/values-ms-rMY/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Lagu sebelumnya" - "Lagu seterusnya" - "Jeda" - "Main" - "Berhenti" - "Gulung semula" - "Mara laju" + "Lagu sebelumnya" + "Lagu seterusnya" + "Jeda" + "Main" + "Berhenti" + "Gulung semula" + "Mara laju" diff --git a/library/src/main/res/values-my-rMM/strings.xml b/library/src/main/res/values-my-rMM/strings.xml index 62e4e6c0c7..4b68e6e950 100644 --- a/library/src/main/res/values-my-rMM/strings.xml +++ b/library/src/main/res/values-my-rMM/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "ယခင် တစ်ပုဒ်" - "နောက် တစ်ပုဒ်" - "ခဏရပ်ရန်" - "ဖွင့်ရန်" - "ရပ်ရန်" - "ပြန်ရစ်ရန်" - "ရှေ့သို့ သွားရန်" + "ယခင် တစ်ပုဒ်" + "နောက် တစ်ပုဒ်" + "ခဏရပ်ရန်" + "ဖွင့်ရန်" + "ရပ်ရန်" + "ပြန်ရစ်ရန်" + "ရှေ့သို့ သွားရန်" diff --git a/library/src/main/res/values-nb/strings.xml b/library/src/main/res/values-nb/strings.xml index b5e7c6d05b..37454235ad 100644 --- a/library/src/main/res/values-nb/strings.xml +++ b/library/src/main/res/values-nb/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Forrige spor" - "Neste spor" - "Sett på pause" - "Spill av" - "Stopp" - "Tilbakespoling" - "Fremoverspoling" + "Forrige spor" + "Neste spor" + "Sett på pause" + "Spill av" + "Stopp" + "Tilbakespoling" + "Fremoverspoling" diff --git a/library/src/main/res/values-ne-rNP/strings.xml b/library/src/main/res/values-ne-rNP/strings.xml index 47ce703544..375e44afce 100644 --- a/library/src/main/res/values-ne-rNP/strings.xml +++ b/library/src/main/res/values-ne-rNP/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "अघिल्लो ट्रयाक" - "अर्को ट्रयाक" - "रोक्नुहोस्" - "चलाउनुहोस्" - "रोक्नुहोस्" - "दोहोर्याउनुहोस्" - "फास्ट फर्वार्ड" + "अघिल्लो ट्रयाक" + "अर्को ट्रयाक" + "रोक्नुहोस्" + "चलाउनुहोस्" + "रोक्नुहोस्" + "दोहोर्याउनुहोस्" + "फास्ट फर्वार्ड" diff --git a/library/src/main/res/values-nl/strings.xml b/library/src/main/res/values-nl/strings.xml index 092165db53..2bdbf0bdae 100644 --- a/library/src/main/res/values-nl/strings.xml +++ b/library/src/main/res/values-nl/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Vorig nummer" - "Volgend nummer" - "Onderbreken" - "Afspelen" - "Stoppen" - "Terugspoelen" - "Vooruitspoelen" + "Vorig nummer" + "Volgend nummer" + "Onderbreken" + "Afspelen" + "Stoppen" + "Terugspoelen" + "Vooruitspoelen" diff --git a/library/src/main/res/values-pa-rIN/strings.xml b/library/src/main/res/values-pa-rIN/strings.xml index 96654b89f0..143508e071 100644 --- a/library/src/main/res/values-pa-rIN/strings.xml +++ b/library/src/main/res/values-pa-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "ਪਿਛਲਾ ਟਰੈਕ" - "ਅਗਲਾ ਟਰੈਕ" - "ਰੋਕੋ" - "ਪਲੇ ਕਰੋ" - "ਰੋਕੋ" - "ਰੀਵਾਈਂਡ ਕਰੋ" - "ਅੱਗੇ ਭੇਜੋ" + "ਪਿਛਲਾ ਟਰੈਕ" + "ਅਗਲਾ ਟਰੈਕ" + "ਰੋਕੋ" + "ਪਲੇ ਕਰੋ" + "ਰੋਕੋ" + "ਰੀਵਾਈਂਡ ਕਰੋ" + "ਅੱਗੇ ਭੇਜੋ" diff --git a/library/src/main/res/values-pl/strings.xml b/library/src/main/res/values-pl/strings.xml index 131e0630f3..64f52d5d09 100644 --- a/library/src/main/res/values-pl/strings.xml +++ b/library/src/main/res/values-pl/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Poprzedni utwór" - "Następny utwór" - "Wstrzymaj" - "Odtwórz" - "Zatrzymaj" - "Przewiń do tyłu" - "Przewiń do przodu" + "Poprzedni utwór" + "Następny utwór" + "Wstrzymaj" + "Odtwórz" + "Zatrzymaj" + "Przewiń do tyłu" + "Przewiń do przodu" diff --git a/library/src/main/res/values-pt-rBR/strings.xml b/library/src/main/res/values-pt-rBR/strings.xml index 3e3ac47be9..51bcf4d723 100644 --- a/library/src/main/res/values-pt-rBR/strings.xml +++ b/library/src/main/res/values-pt-rBR/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Faixa anterior" - "Próxima faixa" - "Pausar" - "Reproduzir" - "Parar" - "Retroceder" - "Avançar" + "Faixa anterior" + "Próxima faixa" + "Pausar" + "Reproduzir" + "Parar" + "Retroceder" + "Avançar" diff --git a/library/src/main/res/values-pt-rPT/strings.xml b/library/src/main/res/values-pt-rPT/strings.xml index 0d0d0ec183..5b3c9131d0 100644 --- a/library/src/main/res/values-pt-rPT/strings.xml +++ b/library/src/main/res/values-pt-rPT/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Faixa anterior" - "Faixa seguinte" - "Interromper" - "Reproduzir" - "Parar" - "Rebobinar" - "Avançar" + "Faixa anterior" + "Faixa seguinte" + "Interromper" + "Reproduzir" + "Parar" + "Rebobinar" + "Avançar" diff --git a/library/src/main/res/values-pt/strings.xml b/library/src/main/res/values-pt/strings.xml index 3e3ac47be9..51bcf4d723 100644 --- a/library/src/main/res/values-pt/strings.xml +++ b/library/src/main/res/values-pt/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Faixa anterior" - "Próxima faixa" - "Pausar" - "Reproduzir" - "Parar" - "Retroceder" - "Avançar" + "Faixa anterior" + "Próxima faixa" + "Pausar" + "Reproduzir" + "Parar" + "Retroceder" + "Avançar" diff --git a/library/src/main/res/values-ro/strings.xml b/library/src/main/res/values-ro/strings.xml index 3cb6730c99..5a7feda78c 100644 --- a/library/src/main/res/values-ro/strings.xml +++ b/library/src/main/res/values-ro/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Melodia anterioară" - "Melodia următoare" - "Pauză" - "Redați" - "Opriți" - "Derulați" - "Derulați rapid înainte" + "Melodia anterioară" + "Melodia următoare" + "Pauză" + "Redați" + "Opriți" + "Derulați" + "Derulați rapid înainte" diff --git a/library/src/main/res/values-ru/strings.xml b/library/src/main/res/values-ru/strings.xml index 59a6923954..da47546a8b 100644 --- a/library/src/main/res/values-ru/strings.xml +++ b/library/src/main/res/values-ru/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Предыдущий трек" - "Следующий трек" - "Приостановить" - "Воспроизвести" - "Остановить" - "Перемотать назад" - "Перемотать вперед" + "Предыдущий трек" + "Следующий трек" + "Приостановить" + "Воспроизвести" + "Остановить" + "Перемотать назад" + "Перемотать вперед" diff --git a/library/src/main/res/values-si-rLK/strings.xml b/library/src/main/res/values-si-rLK/strings.xml index d634b82374..0b579240e8 100644 --- a/library/src/main/res/values-si-rLK/strings.xml +++ b/library/src/main/res/values-si-rLK/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "පෙර ගීතය" - "ඊළඟ ගීතය" - "විරාමය" - "ධාවනය කරන්න" - "නතර කරන්න" - "නැවත ඔතන්න" - "වේගයෙන් ඉදිරියට යන" + "පෙර ගීතය" + "ඊළඟ ගීතය" + "විරාමය" + "ධාවනය කරන්න" + "නතර කරන්න" + "නැවත ඔතන්න" + "වේගයෙන් ඉදිරියට යන" diff --git a/library/src/main/res/values-sk/strings.xml b/library/src/main/res/values-sk/strings.xml index b0965864d6..7596497e06 100644 --- a/library/src/main/res/values-sk/strings.xml +++ b/library/src/main/res/values-sk/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Predchádzajúca stopa" - "Ďalšia stopa" - "Pozastaviť" - "Prehrať" - "Zastaviť" - "Pretočiť späť" - "Pretočiť dopredu" + "Predchádzajúca stopa" + "Ďalšia stopa" + "Pozastaviť" + "Prehrať" + "Zastaviť" + "Pretočiť späť" + "Pretočiť dopredu" diff --git a/library/src/main/res/values-sl/strings.xml b/library/src/main/res/values-sl/strings.xml index f2edda42dd..a77586b50c 100644 --- a/library/src/main/res/values-sl/strings.xml +++ b/library/src/main/res/values-sl/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Prejšnja skladba" - "Naslednja skladba" - "Zaustavi" - "Predvajaj" - "Ustavi" - "Previj nazaj" - "Previj naprej" + "Prejšnja skladba" + "Naslednja skladba" + "Zaustavi" + "Predvajaj" + "Ustavi" + "Previj nazaj" + "Previj naprej" diff --git a/library/src/main/res/values-sq-rAL/strings.xml b/library/src/main/res/values-sq-rAL/strings.xml index 6716a32486..1fb824366d 100644 --- a/library/src/main/res/values-sq-rAL/strings.xml +++ b/library/src/main/res/values-sq-rAL/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Kënga e mëparshme" - "Kënga tjetër" - "Pauzë" - "Luaj" - "Ndalo" - "Kthehu pas" - "Përparo me shpejtësi" + "Kënga e mëparshme" + "Kënga tjetër" + "Pauzë" + "Luaj" + "Ndalo" + "Kthehu pas" + "Përparo me shpejtësi" diff --git a/library/src/main/res/values-sr/strings.xml b/library/src/main/res/values-sr/strings.xml index cfb3d20c6a..175ad4fe7f 100644 --- a/library/src/main/res/values-sr/strings.xml +++ b/library/src/main/res/values-sr/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Претходна песма" - "Следећа песма" - "Пауза" - "Пусти" - "Заустави" - "Премотај уназад" - "Премотај унапред" + "Претходна песма" + "Следећа песма" + "Пауза" + "Пусти" + "Заустави" + "Премотај уназад" + "Премотај унапред" diff --git a/library/src/main/res/values-sv/strings.xml b/library/src/main/res/values-sv/strings.xml index 35b987db48..e6a8960458 100644 --- a/library/src/main/res/values-sv/strings.xml +++ b/library/src/main/res/values-sv/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Föregående spår" - "Nästa spår" - "Pausa" - "Spela upp" - "Avbryt" - "Spola tillbaka" - "Snabbspola framåt" + "Föregående spår" + "Nästa spår" + "Pausa" + "Spela upp" + "Avbryt" + "Spola tillbaka" + "Snabbspola framåt" diff --git a/library/src/main/res/values-sw/strings.xml b/library/src/main/res/values-sw/strings.xml index 32e2799e97..8055b7daff 100644 --- a/library/src/main/res/values-sw/strings.xml +++ b/library/src/main/res/values-sw/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Wimbo uliotangulia" - "Wimbo unaofuata" - "Sitisha" - "Cheza" - "Simamisha" - "Rudisha nyuma" - "Peleka mbele kwa kasi" + "Wimbo uliotangulia" + "Wimbo unaofuata" + "Sitisha" + "Cheza" + "Simamisha" + "Rudisha nyuma" + "Peleka mbele kwa kasi" diff --git a/library/src/main/res/values-ta-rIN/strings.xml b/library/src/main/res/values-ta-rIN/strings.xml index 32d88cf9f0..3eb995d467 100644 --- a/library/src/main/res/values-ta-rIN/strings.xml +++ b/library/src/main/res/values-ta-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "முந்தைய ட்ராக்" - "அடுத்த ட்ராக்" - "இடைநிறுத்து" - "இயக்கு" - "நிறுத்து" - "மீண்டும் காட்டு" - "வேகமாக முன்செல்" + "முந்தைய ட்ராக்" + "அடுத்த ட்ராக்" + "இடைநிறுத்து" + "இயக்கு" + "நிறுத்து" + "மீண்டும் காட்டு" + "வேகமாக முன்செல்" diff --git a/library/src/main/res/values-te-rIN/strings.xml b/library/src/main/res/values-te-rIN/strings.xml index f527c30270..fe7930455a 100644 --- a/library/src/main/res/values-te-rIN/strings.xml +++ b/library/src/main/res/values-te-rIN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "మునుపటి ట్రాక్" - "తదుపరి ట్రాక్" - "పాజ్ చేయి" - "ప్లే చేయి" - "ఆపివేయి" - "రివైండ్ చేయి" - "వేగంగా ఫార్వార్డ్ చేయి" + "మునుపటి ట్రాక్" + "తదుపరి ట్రాక్" + "పాజ్ చేయి" + "ప్లే చేయి" + "ఆపివేయి" + "రివైండ్ చేయి" + "వేగంగా ఫార్వార్డ్ చేయి" diff --git a/library/src/main/res/values-th/strings.xml b/library/src/main/res/values-th/strings.xml index 3b86808ee7..deb2aac87d 100644 --- a/library/src/main/res/values-th/strings.xml +++ b/library/src/main/res/values-th/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "แทร็กก่อนหน้า" - "แทร็กถัดไป" - "หยุดชั่วคราว" - "เล่น" - "หยุด" - "กรอกลับ" - "กรอไปข้างหน้า" + "แทร็กก่อนหน้า" + "แทร็กถัดไป" + "หยุดชั่วคราว" + "เล่น" + "หยุด" + "กรอกลับ" + "กรอไปข้างหน้า" diff --git a/library/src/main/res/values-tl/strings.xml b/library/src/main/res/values-tl/strings.xml index 2381287624..28dcb3267e 100644 --- a/library/src/main/res/values-tl/strings.xml +++ b/library/src/main/res/values-tl/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Nakaraang track" - "Susunod na track" - "I-pause" - "I-play" - "Ihinto" - "I-rewind" - "I-fast forward" + "Nakaraang track" + "Susunod na track" + "I-pause" + "I-play" + "Ihinto" + "I-rewind" + "I-fast forward" diff --git a/library/src/main/res/values-tr/strings.xml b/library/src/main/res/values-tr/strings.xml index cd05be1551..4265d796fe 100644 --- a/library/src/main/res/values-tr/strings.xml +++ b/library/src/main/res/values-tr/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Önceki parça" - "Sonraki parça" - "Duraklat" - "Çal" - "Durdur" - "Geri sar" - "İleri sar" + "Önceki parça" + "Sonraki parça" + "Duraklat" + "Çal" + "Durdur" + "Geri sar" + "İleri sar" diff --git a/library/src/main/res/values-uk/strings.xml b/library/src/main/res/values-uk/strings.xml index ec249b7f9f..487ca07556 100644 --- a/library/src/main/res/values-uk/strings.xml +++ b/library/src/main/res/values-uk/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Попередня композиція" - "Наступна композиція" - "Пауза" - "Відтворити" - "Зупинити" - "Перемотати назад" - "Перемотати вперед" + "Попередня композиція" + "Наступна композиція" + "Пауза" + "Відтворити" + "Зупинити" + "Перемотати назад" + "Перемотати вперед" diff --git a/library/src/main/res/values-ur-rPK/strings.xml b/library/src/main/res/values-ur-rPK/strings.xml index 002bf8e780..55fa908bcd 100644 --- a/library/src/main/res/values-ur-rPK/strings.xml +++ b/library/src/main/res/values-ur-rPK/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "پچھلا ٹریک" - "اگلا ٹریک" - "موقوف کریں" - "چلائیں" - "روکیں" - "ریوائینڈ کریں" - "تیزی سے فارورڈ کریں" + "پچھلا ٹریک" + "اگلا ٹریک" + "موقوف کریں" + "چلائیں" + "روکیں" + "ریوائینڈ کریں" + "تیزی سے فارورڈ کریں" diff --git a/library/src/main/res/values-uz-rUZ/strings.xml b/library/src/main/res/values-uz-rUZ/strings.xml index 5b96983ad5..9cee926844 100644 --- a/library/src/main/res/values-uz-rUZ/strings.xml +++ b/library/src/main/res/values-uz-rUZ/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Avvalgi musiqa" - "Keyingi musiqa" - "To‘xtatib turish" - "Ijro qilish" - "To‘xtatish" - "Orqaga o‘tkazish" - "Oldinga o‘tkazish" + "Avvalgi musiqa" + "Keyingi musiqa" + "To‘xtatib turish" + "Ijro qilish" + "To‘xtatish" + "Orqaga o‘tkazish" + "Oldinga o‘tkazish" diff --git a/library/src/main/res/values-vi/strings.xml b/library/src/main/res/values-vi/strings.xml index 2f0dce399a..917ec8e95c 100644 --- a/library/src/main/res/values-vi/strings.xml +++ b/library/src/main/res/values-vi/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Bản nhạc trước" - "Bản nhạc tiếp theo" - "Tạm dừng" - "Phát" - "Ngừng" - "Tua lại" - "Tua đi" + "Bản nhạc trước" + "Bản nhạc tiếp theo" + "Tạm dừng" + "Phát" + "Ngừng" + "Tua lại" + "Tua đi" diff --git a/library/src/main/res/values-zh-rCN/strings.xml b/library/src/main/res/values-zh-rCN/strings.xml index ebec8de267..41e02409e2 100644 --- a/library/src/main/res/values-zh-rCN/strings.xml +++ b/library/src/main/res/values-zh-rCN/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "上一曲" - "下一曲" - "暂停" - "播放" - "停止" - "快退" - "快进" + "上一曲" + "下一曲" + "暂停" + "播放" + "停止" + "快退" + "快进" diff --git a/library/src/main/res/values-zh-rHK/strings.xml b/library/src/main/res/values-zh-rHK/strings.xml index b97fab76af..a3244bcd70 100644 --- a/library/src/main/res/values-zh-rHK/strings.xml +++ b/library/src/main/res/values-zh-rHK/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒帶" - "向前快轉" + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒帶" + "向前快轉" diff --git a/library/src/main/res/values-zh-rTW/strings.xml b/library/src/main/res/values-zh-rTW/strings.xml index 7b8552f1b6..ee915c5d9d 100644 --- a/library/src/main/res/values-zh-rTW/strings.xml +++ b/library/src/main/res/values-zh-rTW/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒轉" - "快轉" + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒轉" + "快轉" diff --git a/library/src/main/res/values-zu/strings.xml b/library/src/main/res/values-zu/strings.xml index 3ed91417f2..e998846454 100644 --- a/library/src/main/res/values-zu/strings.xml +++ b/library/src/main/res/values-zu/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - "Ithrekhi yangaphambilini" - "Ithrekhi elandelayo" - "Misa isikhashana" - "Dlala" - "Misa" - "Buyisela emumva" - "Ukudlulisa ngokushesha" + "Ithrekhi yangaphambilini" + "Ithrekhi elandelayo" + "Misa isikhashana" + "Dlala" + "Misa" + "Buyisela emumva" + "Ukudlulisa ngokushesha" diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 2aa6fcd86b..b210e30512 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -15,8 +15,7 @@ --> - - + + - diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml index 83bb88cc04..1e652dddb3 100644 --- a/library/src/main/res/values/strings.xml +++ b/library/src/main/res/values/strings.xml @@ -14,12 +14,11 @@ limitations under the License. --> - Previous track - Next track - Pause - Play - Stop - Rewind - Fast forward + Previous track + Next track + Pause + Play + Stop + Rewind + Fast forward - diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index 18109e581c..fe1e26e967 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -15,31 +15,30 @@ --> - - - - - - diff --git a/playbacktests/src/main/AndroidManifest.xml b/playbacktests/src/main/AndroidManifest.xml index 38e8491b31..7dde5b65f8 100644 --- a/playbacktests/src/main/AndroidManifest.xml +++ b/playbacktests/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ + android:versionCode="2001" + android:versionName="2.0.1"> diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorInput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorInput.java index 090d8549be..5cb11fdd81 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorInput.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorInput.java @@ -84,7 +84,7 @@ public final class FakeExtractorInput implements ExtractorInput { * @param position The position to set. */ public void setPosition(int position) { - Assert.assertTrue(0 <= position && position < data.length); + Assert.assertTrue(0 <= position && position <= data.length); readPosition = position; peekPosition = position; } @@ -203,7 +203,7 @@ public final class FakeExtractorInput implements ExtractorInput { peekPosition = readPosition; throw new SimulatedIOException("Simulated IO error at position: " + position); } - if (isEof()) { + if (length > 0 && position == data.length) { if (allowEndOfInput) { return false; } @@ -217,6 +217,10 @@ public final class FakeExtractorInput implements ExtractorInput { } 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; if (simulatePartialReads && requestedLength > 1 && !partiallySatisfiedTargetPositions.get(targetPosition)) { @@ -226,10 +230,6 @@ public final class FakeExtractorInput implements ExtractorInput { return Math.min(requestedLength, data.length - readPosition); } - private boolean isEof() { - return readPosition == data.length; - } - /** * Builder of {@link FakeExtractorInput} instances. */ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java index a66e13cfd6..b399d79e8d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java @@ -88,7 +88,8 @@ public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { } @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); sampleFlags.add(flags); sampleStartOffsets.add(sampleData.length - offset - size);