Merge pull request #3009 from google/dev-v2-r2.4.3

r2.4.3
This commit is contained in:
ojw28 2017-06-30 19:07:03 +01:00 committed by GitHub
commit 2bebd526e1
42 changed files with 620 additions and 102 deletions

View File

@ -1,11 +1,29 @@
# Release notes #
### r2.4.3 ###
* Audio: Workaround custom audio decoders misreporting their maximum supported
channel counts ([#2940](https://github.com/google/ExoPlayer/issues/2940)).
* Audio: Workaround for broken MediaTek raw decoder on some devices
([#2873](https://github.com/google/ExoPlayer/issues/2873)).
* Captions: Fix TTML captions appearing at the top of the screen
([#2953](https://github.com/google/ExoPlayer/issues/2953)).
* Captions: Fix handling of some DVB subtitles
([#2957](https://github.com/google/ExoPlayer/issues/2957)).
* Track selection: Fix setSelectionOverride(index, tracks, null)
([#2988](https://github.com/google/ExoPlayer/issues/2988)).
* GVR extension: Add support for mono input
([#2710](https://github.com/google/ExoPlayer/issues/2710)).
* FLAC extension: Fix failing build
([#2977](https://github.com/google/ExoPlayer/pull/2977)).
* Misc bugfixes.
### r2.4.2 ###
* Stability: Work around Nexus 10 reboot when playing certain content
([2806](https://github.com/google/ExoPlayer/issues/2806)).
([#2806](https://github.com/google/ExoPlayer/issues/2806)).
* MP3: Correctly treat MP3s with INFO headers as constant bitrate
([2895](https://github.com/google/ExoPlayer/issues/2895)).
([#2895](https://github.com/google/ExoPlayer/issues/2895)).
* HLS: Use average rather than peak bandwidth when available
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
* SmoothStreaming: Fix timeline for live streams

View File

@ -48,7 +48,7 @@ allprojects {
releaseRepoName = getBintrayRepo()
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.4.2'
releaseVersion = 'r2.4.3'
releaseWebsite = 'https://github.com/google/ExoPlayer'
}
if (it.hasProperty('externalBuildDir')) {

View File

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

View File

@ -22,6 +22,7 @@
#include <cassert>
#include <cstdlib>
#include <cstring>
#define LOG_TAG "FLACParser"
#define ALOGE(...) \

View File

@ -25,7 +25,7 @@ android {
dependencies {
compile project(':library-core')
compile 'com.google.vr:sdk-audio:1.30.0'
compile 'com.google.vr:sdk-audio:1.60.1'
}
ext {

View File

@ -82,6 +82,9 @@ public final class GvrAudioProcessor implements AudioProcessor {
maybeReleaseGvrAudioSurround();
int surroundFormat;
switch (channelCount) {
case 1:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
break;
case 2:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO;
break;

View File

@ -16,6 +16,8 @@
package com.google.android.exoplayer2.ext.okhttp;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
@ -45,13 +47,14 @@ public class OkHttpDataSource implements HttpDataSource {
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
private final Call.Factory callFactory;
private final String userAgent;
private final Predicate<String> contentTypePredicate;
private final TransferListener<? super OkHttpDataSource> listener;
private final CacheControl cacheControl;
private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties;
@NonNull private final Call.Factory callFactory;
@NonNull private final RequestProperties requestProperties;
@Nullable private final String userAgent;
@Nullable private final Predicate<String> contentTypePredicate;
@Nullable private final TransferListener<? super OkHttpDataSource> listener;
@Nullable private final CacheControl cacheControl;
@Nullable private final RequestProperties defaultRequestProperties;
private DataSpec dataSpec;
private Response response;
@ -67,33 +70,34 @@ public class OkHttpDataSource implements HttpDataSource {
/**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source.
* @param userAgent The User-Agent string that should be used.
* @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
*/
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
Predicate<String> contentTypePredicate) {
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
@Nullable Predicate<String> contentTypePredicate) {
this(callFactory, userAgent, contentTypePredicate, null);
}
/**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source.
* @param userAgent The User-Agent string that should be used.
* @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* @param listener An optional listener.
*/
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener) {
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener) {
this(callFactory, userAgent, contentTypePredicate, listener, null, null);
}
/**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source.
* @param userAgent The User-Agent string that should be used.
* @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
@ -102,11 +106,12 @@ public class OkHttpDataSource implements HttpDataSource {
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to
* the server as HTTP headers on every request.
*/
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener,
CacheControl cacheControl, RequestProperties defaultRequestProperties) {
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener,
@Nullable CacheControl cacheControl, @Nullable RequestProperties defaultRequestProperties) {
this.callFactory = Assertions.checkNotNull(callFactory);
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.userAgent = userAgent;
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.cacheControl = cacheControl;
@ -280,7 +285,10 @@ public class OkHttpDataSource implements HttpDataSource {
}
builder.addHeader("Range", rangeRequest);
}
builder.addHeader("User-Agent", userAgent);
if (userAgent != null) {
builder.addHeader("User-Agent", userAgent);
}
if (!allowGzip) {
builder.addHeader("Accept-Encoding", "identity");
}

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.ext.okhttp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
@ -28,31 +30,32 @@ import okhttp3.Call;
*/
public final class OkHttpDataSourceFactory extends BaseFactory {
private final Call.Factory callFactory;
private final String userAgent;
private final TransferListener<? super DataSource> listener;
private final CacheControl cacheControl;
@NonNull private final Call.Factory callFactory;
@Nullable private final String userAgent;
@Nullable private final TransferListener<? super DataSource> listener;
@Nullable private final CacheControl cacheControl;
/**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the sources created by the factory.
* @param userAgent The User-Agent string that should be used.
* @param userAgent An optional User-Agent string.
* @param listener An optional listener.
*/
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
TransferListener<? super DataSource> listener) {
public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
@Nullable TransferListener<? super DataSource> listener) {
this(callFactory, userAgent, listener, null);
}
/**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the sources created by the factory.
* @param userAgent The User-Agent string that should be used.
* @param userAgent An optional User-Agent string.
* @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
*/
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
TransferListener<? super DataSource> listener, CacheControl cacheControl) {
public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
@Nullable TransferListener<? super DataSource> listener,
@Nullable CacheControl cacheControl) {
this.callFactory = callFactory;
this.userAgent = userAgent;
this.listener = listener;

View File

@ -480,11 +480,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
protected void onStarted() {
droppedFrames = 0;
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
joiningDeadlineMs = C.TIME_UNSET;
}
@Override
protected void onStopped() {
joiningDeadlineMs = C.TIME_UNSET;
maybeNotifyDroppedFrames();
}

View File

@ -24,6 +24,9 @@
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner"/>
<provider
android:authorities="com.google.android.exoplayer2.core.test"
android:name="com.google.android.exoplayer2.upstream.ContentDataSourceTest$TestContentProvider"/>
</application>
<instrumentation

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -8,7 +8,7 @@ track 0:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = audio/x-flac
sampleMimeType = audio/flac
maxInputSize = 768000
width = -1
height = -1

View File

@ -179,9 +179,13 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
assertEquals(1, output.size());
ttmlCue = output.get(0);
assertEquals("dolor", ttmlCue.text.toString());
assertEquals(10f / 100f, ttmlCue.position);
assertEquals(80f / 100f, ttmlCue.line);
assertEquals(1f, ttmlCue.size);
assertEquals(Cue.DIMEN_UNSET, ttmlCue.position);
assertEquals(Cue.DIMEN_UNSET, ttmlCue.line);
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
// assertEquals(10f / 100f, ttmlCue.position);
// assertEquals(80f / 100f, ttmlCue.line);
// assertEquals(1f, ttmlCue.size);
output = subtitle.getCues(21000000);
assertEquals(1, output.size());

View File

@ -0,0 +1,196 @@
/*
* 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.trackselection;
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.MimeTypes;
import junit.framework.TestCase;
/**
* Unit tests for {@link MappingTrackSelector}.
*/
public final class MappingTrackSelectorTest extends TestCase {
private static final RendererCapabilities VIDEO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO);
private static final RendererCapabilities AUDIO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
private static final RendererCapabilities[] RENDERER_CAPABILITIES = new RendererCapabilities[] {
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES
};
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup(
Format.createVideoSampleFormat("video", MimeTypes.VIDEO_H264, null, Format.NO_VALUE,
Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, null));
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(
Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
Format.NO_VALUE, 2, 44100, null, null, 0, null));
private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray(
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
private static final TrackSelection[] TRACK_SELECTIONS = new TrackSelection[] {
new FixedTrackSelection(VIDEO_TRACK_GROUP, 0),
new FixedTrackSelection(AUDIO_TRACK_GROUP, 0)
};
/**
* Tests that the video and audio track groups are mapped onto the correct renderers.
*/
public void testMapping() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
}
/**
* Tests that the video and audio track groups are mapped onto the correct renderers when the
* renderer ordering is reversed.
*/
public void testMappingReverseOrder() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] {
AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS);
trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP);
}
/**
* Tests video and audio track groups are mapped onto the correct renderers when there are
* multiple track groups of the same type.
*/
public void testMappingMulti() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP,
VIDEO_TRACK_GROUP);
trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
}
/**
* Tests the result of {@link MappingTrackSelector#selectTracks(RendererCapabilities[],
* TrackGroupArray[], int[][][])} is propagated correctly to the result of
* {@link MappingTrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray)}.
*/
public void testSelectTracks() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that a null override clears a track selection.
*/
public void testSelectTracksWithNullOverride() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertNull(result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that a null override can be cleared.
*/
public void testSelectTracksWithClearedNullOverride() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
trackSelector.clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP));
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that an override is not applied for a different set of available track groups.
*/
public void testSelectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES,
new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP));
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* A {@link MappingTrackSelector} that returns a fixed result from
* {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}.
*/
private static final class FakeMappingTrackSelector extends MappingTrackSelector {
private final TrackSelection[] result;
private TrackGroupArray[] lastRendererTrackGroupArrays;
public FakeMappingTrackSelector(TrackSelection... result) {
this.result = result.length == 0 ? null : result;
}
@Override
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities,
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
throws ExoPlaybackException {
lastRendererTrackGroupArrays = rendererTrackGroupArrays;
return result == null ? new TrackSelection[rendererCapabilities.length] : result;
}
public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) {
assertEquals(expected.length, lastRendererTrackGroupArrays[rendererIndex].length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], lastRendererTrackGroupArrays[rendererIndex].get(i));
}
}
}
/**
* A {@link RendererCapabilities} that advertises adaptive support for all tracks of a given type.
*/
private static final class FakeRendererCapabilities implements RendererCapabilities {
private final int trackType;
public FakeRendererCapabilities(int trackType) {
this.trackType = trackType;
}
@Override
public int getTrackType() {
return trackType;
}
@Override
public int supportsFormat(Format format) throws ExoPlaybackException {
return MimeTypes.getTrackType(format.sampleMimeType) == trackType
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
}
@Override
public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
return ADAPTIVE_SEAMLESS;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2017 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.upstream;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
/**
* Unit tests for {@link AssetDataSource}.
*/
public final class AssetDataSourceTest extends InstrumentationTestCase {
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
public void testReadFileUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
public void testReadAssetUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2017 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.upstream;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Unit tests for {@link ContentDataSource}.
*/
public final class ContentDataSourceTest extends InstrumentationTestCase {
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
public void testReadValidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.path(DATA_PATH).build();
DataSpec dataSpec = new DataSpec(contentUri);
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
public void testReadInvalidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.build();
DataSpec dataSpec = new DataSpec(contentUri);
try {
dataSource.open(dataSpec);
fail();
} catch (ContentDataSource.ContentDataSourceException e) {
// Expected.
assertTrue(e.getCause() instanceof FileNotFoundException);
} finally {
dataSource.close();
}
}
/**
* A {@link ContentProvider} for the test.
*/
public static final class TestContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException();
}
@Override
public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
if (uri.getPath() == null) {
return null;
}
try {
return getContext().getAssets().openFd(uri.getPath().replaceFirst("/", ""));
} catch (IOException e) {
FileNotFoundException exception = new FileNotFoundException(e.getMessage());
exception.initCause(e);
throw exception;
}
}
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException();
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(@NonNull Uri uri, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(@NonNull Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo {
* The version of the library expressed as a string, for example "1.2.3".
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
String VERSION = "2.4.2";
String VERSION = "2.4.3";
/**
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
String VERSION_SLASHY = "ExoPlayerLib/2.4.2";
String VERSION_SLASHY = "ExoPlayerLib/2.4.3";
/**
* The version of the library expressed as an integer, for example 1002003.
@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
int VERSION_INT = 2004002;
int VERSION_INT = 2004003;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -1292,7 +1292,7 @@ public final class AudioTrack {
// The timestamp time base is probably wrong.
String message = "Spurious audio timestamp (system clock mismatch): "
+ audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", "
+ playbackPositionUs;
+ playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames();
if (failOnSpuriousAudioTimestamp) {
throw new InvalidAudioTrackTimestampException(message);
}
@ -1303,7 +1303,7 @@ public final class AudioTrack {
// The timestamp frame position is probably wrong.
String message = "Spurious audio timestamp (frame position mismatch): "
+ audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", "
+ playbackPositionUs;
+ playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames();
if (failOnSpuriousAudioTimestamp) {
throw new InvalidAudioTrackTimestampException(message);
}

View File

@ -28,11 +28,11 @@ import java.util.Map;
@TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> {
/** Wraps the exception which is the cause of the error state. */
/** Wraps the throwable which is the cause of the error state. */
class DrmSessionException extends Exception {
public DrmSessionException(Exception e) {
super(e);
public DrmSessionException(Throwable cause) {
super(cause);
}
}

View File

@ -67,7 +67,7 @@ import java.util.Collections;
hasOutputFormat = true;
} else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) {
String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW
: MimeTypes.AUDIO_ULAW;
: MimeTypes.AUDIO_MLAW;
int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT;
Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE,
Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null);

View File

@ -264,7 +264,9 @@ public final class MediaCodecInfo {
logNoSupport("channelCount.aCaps");
return false;
}
if (audioCapabilities.getMaxInputChannelCount() < channelCount) {
int maxInputChannelCount = adjustMaxInputChannelCount(name, mimeType,
audioCapabilities.getMaxInputChannelCount());
if (maxInputChannelCount < channelCount) {
logNoSupport("channelCount.support, " + channelCount);
return false;
}
@ -281,6 +283,40 @@ public final class MediaCodecInfo {
+ Util.DEVICE_DEBUG_INFO + "]");
}
private static int adjustMaxInputChannelCount(String name, String mimeType, int maxChannelCount) {
if (maxChannelCount > 1 || (Util.SDK_INT >= 26 && maxChannelCount > 0)) {
// The maximum channel count looks like it's been set correctly.
return maxChannelCount;
}
if (MimeTypes.AUDIO_MPEG.equals(mimeType)
|| MimeTypes.AUDIO_AMR_NB.equals(mimeType)
|| MimeTypes.AUDIO_AMR_WB.equals(mimeType)
|| MimeTypes.AUDIO_AAC.equals(mimeType)
|| MimeTypes.AUDIO_VORBIS.equals(mimeType)
|| MimeTypes.AUDIO_OPUS.equals(mimeType)
|| MimeTypes.AUDIO_RAW.equals(mimeType)
|| MimeTypes.AUDIO_FLAC.equals(mimeType)
|| MimeTypes.AUDIO_ALAW.equals(mimeType)
|| MimeTypes.AUDIO_MLAW.equals(mimeType)
|| MimeTypes.AUDIO_MSGSM.equals(mimeType)) {
// Platform code should have set a default.
return maxChannelCount;
}
// The maximum channel count looks incorrect. Adjust it to an assumed default.
int assumedMaxChannelCount;
if (MimeTypes.AUDIO_AC3.equals(mimeType)) {
assumedMaxChannelCount = 6;
} else if (MimeTypes.AUDIO_E_AC3.equals(mimeType)) {
assumedMaxChannelCount = 16;
} else {
// Default to the platform limit, which is 30.
assumedMaxChannelCount = 30;
}
Log.w(TAG, "AssumedMaxChannelAdjustment: " + name + ", [" + maxChannelCount + " to "
+ assumedMaxChannelCount + "]");
return assumedMaxChannelCount;
}
private static boolean isAdaptive(CodecCapabilities capabilities) {
return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities);
}

View File

@ -56,8 +56,10 @@ public final class MediaCodecUtil {
}
private static final String TAG = "MediaCodecUtil";
private static final String GOOGLE_RAW_DECODER_NAME = "OMX.google.raw.decoder";
private static final String MTK_RAW_DECODER_NAME = "OMX.MTK.AUDIO.DECODER.RAW";
private static final MediaCodecInfo PASSTHROUGH_DECODER_INFO =
MediaCodecInfo.newPassthroughInstance("OMX.google.raw.decoder");
MediaCodecInfo.newPassthroughInstance(GOOGLE_RAW_DECODER_NAME);
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>();
@ -155,6 +157,7 @@ public final class MediaCodecUtil {
+ ". Assuming: " + decoderInfos.get(0).name);
}
}
applyWorkarounds(decoderInfos);
decoderInfos = Collections.unmodifiableList(decoderInfos);
decoderInfosCache.put(key, decoderInfos);
return decoderInfos;
@ -339,6 +342,27 @@ public final class MediaCodecUtil {
return true;
}
/**
* Modifies a list of {@link MediaCodecInfo}s to apply workarounds where we know better than the
* platform.
*
* @param decoderInfos The list to modify.
*/
private static void applyWorkarounds(List<MediaCodecInfo> decoderInfos) {
if (Util.SDK_INT < 26 && decoderInfos.size() > 1
&& MTK_RAW_DECODER_NAME.equals(decoderInfos.get(0).name)) {
// Prefer the Google raw decoder over the MediaTek one [Internal: b/62337687].
for (int i = 1; i < decoderInfos.size(); i++) {
MediaCodecInfo decoderInfo = decoderInfos.get(i);
if (GOOGLE_RAW_DECODER_NAME.equals(decoderInfo.name)) {
decoderInfos.remove(i);
decoderInfos.add(0, decoderInfo);
break;
}
}
}
}
/**
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
* adaptive decoder.

View File

@ -106,6 +106,8 @@ public interface MediaPeriod extends SequenceableLoader {
/**
* Discards buffered media up to the specified position.
* <p>
* This method should only be called after the period has been prepared.
*
* @param positionUs The position in microseconds.
*/
@ -116,6 +118,8 @@ public interface MediaPeriod extends SequenceableLoader {
* <p>
* After this method has returned a value other than {@link C#TIME_UNSET}, all
* {@link SampleStream}s provided by the period are guaranteed to start from a key frame.
* <p>
* This method should only be called after the period has been prepared.
*
* @return If a discontinuity was read then the playback position in microseconds after the
* discontinuity. Else {@link C#TIME_UNSET}.

View File

@ -42,7 +42,7 @@ public final class TrackGroup {
private int hashCode;
/**
* @param formats The track formats. Must not be null or contain null elements.
* @param formats The track formats. Must not be null, contain null elements or be of length 0.
*/
public TrackGroup(Format... formats) {
Assertions.checkState(formats.length > 0);

View File

@ -667,13 +667,15 @@ import java.util.List;
int runLength = 0;
int clutIndex = 0;
int peek = data.readBits(2);
if (!data.readBit()) {
if (peek != 0x00) {
runLength = 1;
clutIndex = peek;
} else if (data.readBit()) {
runLength = 3 + data.readBits(3);
clutIndex = data.readBits(2);
} else if (!data.readBit()) {
} else if (data.readBit()) {
runLength = 1;
} else {
switch (data.readBits(2)) {
case 0x00:
endOfPixelCodeString = true;

View File

@ -222,9 +222,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
/**
* Parses a region declaration.
* <p>
* If the region defines an origin and/or extent, it is required that they're defined as
* percentages of the viewport. Region declarations that define origin and/or extent in other
* formats are unsupported, and null is returned.
* If the region defines an origin and extent, it is required that they're defined as percentages
* of the viewport. Region declarations that define origin and extent in other formats are
* unsupported, and null is returned.
*/
private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) {
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
@ -250,9 +250,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
return null;
}
} else {
Log.w(TAG, "Ignoring region without an origin");
return null;
// TODO: Should default to top left as below in this case, but need to fix
// https://github.com/google/ExoPlayer/issues/2953 first.
// Origin is omitted. Default to top left.
position = 0;
line = 0;
// position = 0;
// line = 0;
}
float width;
@ -273,9 +277,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
return null;
}
} else {
Log.w(TAG, "Ignoring region without an extent");
return null;
// TODO: Should default to extent of parent as below in this case, but need to fix
// https://github.com/google/ExoPlayer/issues/2953 first.
// Extent is omitted. Default to extent of parent.
width = 1;
height = 1;
// width = 1;
// height = 1;
}
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;

View File

@ -304,10 +304,10 @@ public abstract class MappingTrackSelector extends TrackSelector {
trackSelections[i] = null;
} else {
TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i];
Map<TrackGroupArray, SelectionOverride> overrides = selectionOverrides.get(i);
SelectionOverride override = overrides == null ? null : overrides.get(rendererTrackGroup);
if (override != null) {
trackSelections[i] = override.createTrackSelection(rendererTrackGroup);
if (hasSelectionOverride(i, rendererTrackGroup)) {
SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup);
trackSelections[i] = override == null ? null
: override.createTrackSelection(rendererTrackGroup);
}
}
}

View File

@ -22,6 +22,7 @@ import android.net.Uri;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@ -71,9 +72,13 @@ public final class ContentDataSource implements DataSource {
try {
uri = dataSpec.uri;
assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r");
if (assetFileDescriptor == null) {
throw new FileNotFoundException("Could not open file descriptor for: " + uri);
}
inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
long skipped = inputStream.skip(dataSpec.position);
if (skipped < dataSpec.position) {
long assertStartOffset = assetFileDescriptor.getStartOffset();
long skipped = inputStream.skip(assertStartOffset + dataSpec.position) - assertStartOffset;
if (skipped != dataSpec.position) {
// We expect the skip to be satisfied in full. If it isn't then we're probably trying to
// skip beyond the end of the data.
throw new EOFException();
@ -81,12 +86,16 @@ public final class ContentDataSource implements DataSource {
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else {
bytesRemaining = inputStream.available();
if (bytesRemaining == 0) {
// FileInputStream.available() returns 0 if the remaining length cannot be determined, or
// if it's greater than Integer.MAX_VALUE. We don't know the true length in either case,
// so treat as unbounded.
bytesRemaining = C.LENGTH_UNSET;
bytesRemaining = assetFileDescriptor.getLength();
if (bytesRemaining == AssetFileDescriptor.UNKNOWN_LENGTH) {
// The asset must extend to the end of the file.
bytesRemaining = inputStream.available();
if (bytesRemaining == 0) {
// FileInputStream.available() returns 0 if the remaining length cannot be determined,
// or if it's greater than Integer.MAX_VALUE. We don't know the true length in either
// case, so treat as unbounded.
bytesRemaining = C.LENGTH_UNSET;
}
}
}
} catch (IOException e) {

View File

@ -48,7 +48,7 @@ public final class MimeTypes {
public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2";
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw";
public static final String AUDIO_ALAW = BASE_TYPE_AUDIO + "/g711-alaw";
public static final String AUDIO_ULAW = BASE_TYPE_AUDIO + "/g711-mlaw";
public static final String AUDIO_MLAW = BASE_TYPE_AUDIO + "/g711-mlaw";
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3";
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
@ -59,8 +59,9 @@ public final class MimeTypes {
public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus";
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac";
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";

View File

@ -260,11 +260,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
super.onStarted();
droppedFrames = 0;
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
joiningDeadlineMs = C.TIME_UNSET;
}
@Override
protected void onStopped() {
joiningDeadlineMs = C.TIME_UNSET;
maybeNotifyDroppedFrames();
super.onStopped();
}

View File

@ -30,6 +30,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
@ -174,6 +175,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
throws IOException {
HashSet<String> variantUrls = new HashSet<>();
ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>();
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
@ -251,11 +253,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
width = Format.NO_VALUE;
height = Format.NO_VALUE;
}
line = iterator.next();
Format format = Format.createVideoContainerFormat(Integer.toString(variants.size()),
MimeTypes.APPLICATION_M3U8, null, codecs, bitrate, width, height, Format.NO_VALUE, null,
0);
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
line = iterator.next(); // #EXT-X-STREAM-INF's URI.
if (variantUrls.add(line)) {
Format format = Format.createVideoContainerFormat(Integer.toString(variants.size()),
MimeTypes.APPLICATION_M3U8, null, codecs, bitrate, width, height, Format.NO_VALUE,
null, 0);
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
}
}
}
if (noClosedCaptions) {

View File

@ -89,7 +89,6 @@ public class DefaultTimeBar extends View implements TimeBar {
private final Formatter formatter;
private final Runnable stopScrubbingRunnable;
private int scrubberSize;
private OnScrubListener listener;
private int keyCountIncrement;
private long keyTimeIncrement;
@ -184,7 +183,6 @@ public class DefaultTimeBar extends View implements TimeBar {
stopScrubbing(false);
}
};
scrubberSize = scrubberEnabledSize;
scrubberPadding =
(Math.max(scrubberDisabledSize, Math.max(scrubberEnabledSize, scrubberDraggedSize)) + 1)
/ 2;
@ -234,8 +232,6 @@ public class DefaultTimeBar extends View implements TimeBar {
this.duration = duration;
if (scrubbing && duration == C.TIME_UNSET) {
stopScrubbing(true);
} else {
updateScrubberState();
}
update();
}
@ -251,7 +247,6 @@ public class DefaultTimeBar extends View implements TimeBar {
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateScrubberState();
if (scrubbing && !enabled) {
stopScrubbing(true);
}
@ -436,7 +431,6 @@ public class DefaultTimeBar extends View implements TimeBar {
private void startScrubbing() {
scrubbing = true;
updateScrubberState();
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
@ -452,18 +446,12 @@ public class DefaultTimeBar extends View implements TimeBar {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(false);
}
updateScrubberState();
invalidate();
if (listener != null) {
listener.onScrubStop(this, getScrubberPosition(), canceled);
}
}
private void updateScrubberState() {
scrubberSize = scrubbing ? scrubberDraggedSize
: (isEnabled() && duration >= 0 ? scrubberEnabledSize : scrubberDisabledSize);
}
private void update() {
bufferedBar.set(progressBar);
scrubberBar.set(progressBar);
@ -543,6 +531,8 @@ public class DefaultTimeBar extends View implements TimeBar {
if (duration <= 0) {
return;
}
int scrubberSize = (scrubbing || isFocused()) ? scrubberDraggedSize
: (isEnabled() ? scrubberEnabledSize : scrubberDisabledSize);
int playheadRadius = scrubberSize / 2;
int playheadCenter = Util.constrainValue(scrubberBar.right, scrubberBar.left,
progressBar.right);

View File

@ -17,11 +17,14 @@ package com.google.android.exoplayer2.testutil;
import android.app.Instrumentation;
import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
@ -64,6 +67,22 @@ public class TestUtil {
}
}
public static byte[] readToEnd(DataSource dataSource) throws IOException {
byte[] data = new byte[1024];
int position = 0;
int bytesRead = 0;
while (bytesRead != C.RESULT_END_OF_INPUT) {
if (position == data.length) {
data = Arrays.copyOf(data, data.length * 2);
}
bytesRead = dataSource.read(data, position, data.length - position);
if (bytesRead != C.RESULT_END_OF_INPUT) {
position += bytesRead;
}
}
return Arrays.copyOf(data, position);
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
long timeUs) throws IOException, InterruptedException {
return consumeTestData(extractor, input, timeUs, false);
@ -373,4 +392,24 @@ public class TestUtil {
}
}
/**
* Asserts that data read from a {@link DataSource} matches {@code expected}.
*
* @param dataSource The {@link DataSource} through which to read.
* @param dataSpec The {@link DataSpec} to use when opening the {@link DataSource}.
* @param expectedData The expected data.
* @throws IOException If an error occurs reading fom the {@link DataSource}.
*/
public static void assertDataSourceContent(DataSource dataSource, DataSpec dataSpec,
byte[] expectedData) throws IOException {
try {
long length = dataSource.open(dataSpec);
Assert.assertEquals(length, expectedData.length);
byte[] readData = TestUtil.readToEnd(dataSource);
MoreAsserts.assertEquals(expectedData, readData);
} finally {
dataSource.close();
}
}
}