Commit 42cf213a by olly Committed by kim-vde

Media2 tests: Simplify code

- Use ExoPlayer DataSource instrumentation to intercept reads
- Use ExoPlayer Resource URIs
- Use ExoPlayer DefaultMediaSourceFactory

PiperOrigin-RevId: 326912324
parent 94b66f4c
......@@ -58,7 +58,7 @@ public class MediaSessionUtilTest {
SessionPlayerConnector sessionPlayerConnector = playerTestRule.getSessionPlayerConnector();
MediaSession.SessionCallback sessionCallback =
new SessionCallbackBuilder(context, sessionPlayerConnector).build();
TestUtils.loadResource(context, R.raw.audio, sessionPlayerConnector);
TestUtils.loadResource(R.raw.audio, sessionPlayerConnector);
ListenableFuture<PlayerResult> prepareResult = sessionPlayerConnector.prepare();
CountDownLatch latch = new CountDownLatch(1);
sessionPlayerConnector.registerPlayerCallback(
......
......@@ -27,7 +27,8 @@ import com.google.android.exoplayer2.ext.media2.test.R;
import com.google.android.exoplayer2.util.Util;
/** Stub activity to play media contents on. */
public class MediaStubActivity extends Activity {
public final class MediaStubActivity extends Activity {
private static final String TAG = "MediaStubActivity";
@Override
......
......@@ -17,22 +17,41 @@
package com.google.android.exoplayer2.ext.media2;
import android.content.Context;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.rules.ExternalResource;
/** Rule for tests that use {@link SessionPlayerConnector}. */
public class PlayerTestRule extends ExternalResource {
/* package */ final class PlayerTestRule extends ExternalResource {
/** Instrumentation to attach to {@link DataSource} instances used by the player. */
public interface DataSourceInstrumentation {
/** Called at the start of {@link DataSource#open}. */
void onPreOpen(DataSpec dataSpec);
}
private Context context;
private ExecutorService executor;
private SessionPlayerConnector sessionPlayerConnector;
private SimpleExoPlayer exoPlayer;
@Nullable private DataSourceInstrumentation dataSourceInstrumentation;
@Override
protected void before() {
......@@ -48,15 +67,15 @@ public class PlayerTestRule extends ExternalResource {
// Without posting this, audio focus listeners wouldn't be called because the
// listeners would be posted to the test thread (here) where it waits until the
// tests are finished.
AudioManager audioManager =
(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
context.getSystemService(Context.AUDIO_SERVICE);
DefaultMediaItemConverter converter = new DefaultMediaItemConverter(context);
DataSource.Factory dataSourceFactory = new InstrumentingDataSourceFactory(context);
exoPlayer =
new SimpleExoPlayer.Builder(context)
.setLooper(Looper.myLooper())
.setMediaSourceFactory(converter)
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory, null))
.build();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter(context);
sessionPlayerConnector = new SessionPlayerConnector(exoPlayer, converter);
});
}
......@@ -81,6 +100,11 @@ public class PlayerTestRule extends ExternalResource {
}
}
public void setDataSourceInstrumentation(
@Nullable DataSourceInstrumentation dataSourceInstrumentation) {
this.dataSourceInstrumentation = dataSourceInstrumentation;
}
public ExecutorService getExecutor() {
return executor;
}
......@@ -92,4 +116,66 @@ public class PlayerTestRule extends ExternalResource {
public SimpleExoPlayer getSimpleExoPlayer() {
return exoPlayer;
}
private final class InstrumentingDataSourceFactory implements DataSource.Factory {
private final DefaultDataSourceFactory defaultDataSourceFactory;
public InstrumentingDataSourceFactory(Context context) {
defaultDataSourceFactory =
new DefaultDataSourceFactory(context, Util.getUserAgent(context, "media2-test"));
}
@Override
public DataSource createDataSource() {
DataSource dataSource = defaultDataSourceFactory.createDataSource();
return dataSourceInstrumentation == null
? dataSource
: new InstrumentedDataSource(dataSource, dataSourceInstrumentation);
}
}
private static final class InstrumentedDataSource implements DataSource {
private final DataSource wrappedDataSource;
private final DataSourceInstrumentation instrumentation;
public InstrumentedDataSource(
DataSource wrappedDataSource, DataSourceInstrumentation instrumentation) {
this.wrappedDataSource = wrappedDataSource;
this.instrumentation = instrumentation;
}
@Override
public void addTransferListener(TransferListener transferListener) {
wrappedDataSource.addTransferListener(transferListener);
}
@Override
public long open(DataSpec dataSpec) throws IOException {
instrumentation.onPreOpen(dataSpec);
return wrappedDataSource.open(dataSpec);
}
@Nullable
@Override
public Uri getUri() {
return wrappedDataSource.getUri();
}
@Override
public Map<String, List<String>> getResponseHeaders() {
return wrappedDataSource.getResponseHeaders();
}
@Override
public int read(byte[] target, int offset, int length) throws IOException {
return wrappedDataSource.read(target, offset, length);
}
@Override
public void close() throws IOException {
wrappedDataSource.close();
}
}
}
......@@ -28,8 +28,6 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.DataSourceCallback;
import androidx.media2.common.MediaItem;
import androidx.media2.common.Rating;
import androidx.media2.common.SessionPlayer;
......@@ -47,6 +45,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.media2.test.R;
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
......@@ -99,8 +98,7 @@ public class SessionCallbackBuilderTest {
createMediaSession(
sessionPlayerConnector,
new SessionCallbackBuilder(context, sessionPlayerConnector).build())) {
assertPlayerResultSuccess(
sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem(context)));
assertPlayerResultSuccess(sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem()));
assertPlayerResultSuccess(sessionPlayerConnector.prepare());
OnConnectedListener listener =
......@@ -140,8 +138,7 @@ public class SessionCallbackBuilderTest {
.setFastForwardIncrementMs(testFastForwardIncrementMs)
.setMediaItemProvider(new SessionCallbackBuilder.DefaultMediaItemProvider())
.build())) {
assertPlayerResultSuccess(
sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem(context)));
assertPlayerResultSuccess(sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem()));
assertPlayerResultSuccess(sessionPlayerConnector.prepare());
CountDownLatch latch = new CountDownLatch(1);
......@@ -168,8 +165,8 @@ public class SessionCallbackBuilderTest {
@Test
public void allowedCommand_whenPlaylistSet_allowsSkipTo() throws Exception {
List<MediaItem> testPlaylist = new ArrayList<>();
testPlaylist.add(TestUtils.createMediaItem(context, R.raw.video_desks));
testPlaylist.add(TestUtils.createMediaItem(context, R.raw.video_not_seekable));
testPlaylist.add(TestUtils.createMediaItem(R.raw.video_desks));
testPlaylist.add(TestUtils.createMediaItem(R.raw.video_not_seekable));
int testRewindIncrementMs = 100;
int testFastForwardIncrementMs = 100;
......@@ -238,36 +235,22 @@ public class SessionCallbackBuilderTest {
public void allowedCommand_afterCurrentMediaItemPrepared_notifiesSeekToAvailable()
throws Exception {
List<MediaItem> testPlaylist = new ArrayList<>();
testPlaylist.add(TestUtils.createMediaItem(context));
testPlaylist.add(TestUtils.createMediaItem(R.raw.video_desks));
UriMediaItem secondPlaylistItem = TestUtils.createMediaItem(R.raw.video_big_buck_bunny);
testPlaylist.add(secondPlaylistItem);
int resid = R.raw.video_big_buck_bunny;
TestDataSourceCallback source =
TestDataSourceCallback.fromAssetFd(context.getResources().openRawResourceFd(resid));
CountDownLatch readAllowedLatch = new CountDownLatch(1);
DataSourceCallback dataSource =
new DataSourceCallback() {
@Override
public int readAt(long position, byte[] buffer, int offset, int size) {
playerTestRule.setDataSourceInstrumentation(
dataSpec -> {
if (dataSpec.uri.equals(secondPlaylistItem.getUri())) {
try {
assertThat(readAllowedLatch.await(PLAYER_STATE_CHANGE_WAIT_TIME_MS, MILLISECONDS))
.isTrue();
} catch (Exception e) {
assertWithMessage("Unexpected exception %s", e).fail();
}
return source.readAt(position, buffer, offset, size);
}
@Override
public long getSize() {
return source.getSize();
}
@Override
public void close() {
source.close();
}
};
testPlaylist.add(new CallbackMediaItem.Builder(dataSource).build());
});
try (MediaSession session =
createMediaSession(
......@@ -388,7 +371,7 @@ public class SessionCallbackBuilderTest {
int testSeekPosition = 2_000;
int testRewindIncrementMs = 500;
TestUtils.loadResource(context, testResId, sessionPlayerConnector);
TestUtils.loadResource(testResId, sessionPlayerConnector);
// seekTo() sometimes takes couple of seconds. Disable default timeout behavior.
try (MediaSession session =
......@@ -432,7 +415,7 @@ public class SessionCallbackBuilderTest {
int testSeekPosition = 2_000;
int testFastForwardIncrementMs = 300;
TestUtils.loadResource(context, testResId, sessionPlayerConnector);
TestUtils.loadResource(testResId, sessionPlayerConnector);
// seekTo() sometimes takes couple of seconds. Disable default timeout behavior.
try (MediaSession session =
......@@ -469,15 +452,14 @@ public class SessionCallbackBuilderTest {
@Test
public void setMediaItemProvider_withMediaItemProvider_receivesOnCreateMediaItem()
throws Exception {
int testResId = R.raw.audio;
Uri testMediaIdUri = TestUtils.createResourceUri(context, testResId);
Uri testMediaUri = RawResourceDataSource.buildRawResourceUri(R.raw.audio);
CountDownLatch providerLatch = new CountDownLatch(1);
SessionCallbackBuilder.DefaultMediaItemProvider defaultMediaItemProvider =
new SessionCallbackBuilder.DefaultMediaItemProvider();
SessionCallbackBuilder.MediaItemProvider provider =
(session, controllerInfo, mediaId) -> {
assertThat(mediaId).isEqualTo(testMediaIdUri.toString());
assertThat(mediaId).isEqualTo(testMediaUri.toString());
providerLatch.countDown();
return defaultMediaItemProvider.onCreateMediaItem(session, controllerInfo, mediaId);
};
......@@ -489,7 +471,7 @@ public class SessionCallbackBuilderTest {
@Override
public void onCurrentMediaItemChanged(
@NonNull SessionPlayer player, @NonNull MediaItem item) {
assertThat(((UriMediaItem) item).getUri()).isEqualTo(testMediaIdUri);
assertThat(((UriMediaItem) item).getUri()).isEqualTo(testMediaUri);
currentMediaItemChangedLatch.countDown();
}
});
......@@ -502,7 +484,7 @@ public class SessionCallbackBuilderTest {
.build())) {
try (MediaController controller = createConnectedController(session)) {
assertSessionResultSuccess(
controller.setMediaItem(testMediaIdUri.toString()),
controller.setMediaItem(testMediaUri.toString()),
PLAYER_STATE_CHANGE_OVER_SESSION_WAIT_TIME_MS);
assertThat(providerLatch.await(0, MILLISECONDS)).isTrue();
assertThat(
......
/*
* Copyright 2019 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.ext.media2;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
import androidx.media2.common.DataSourceCallback;
import java.io.IOException;
import java.io.InputStream;
/** A DataSourceCallback that reads from a byte array for use in tests. */
public class TestDataSourceCallback extends DataSourceCallback {
private static final String TAG = "TestDataSourceCallback";
private byte[] data;
// Read an asset fd into a new byte array media item. Closes afd.
public static TestDataSourceCallback fromAssetFd(AssetFileDescriptor afd) throws IOException {
try {
InputStream in = afd.createInputStream();
int size = (int) afd.getDeclaredLength();
byte[] data = new byte[size];
int writeIndex = 0;
int numRead;
do {
numRead = in.read(data, writeIndex, size - writeIndex);
writeIndex += numRead;
} while (numRead >= 0);
return new TestDataSourceCallback(data);
} finally {
afd.close();
}
}
public TestDataSourceCallback(byte[] data) {
this.data = data;
}
@Override
public synchronized int readAt(long position, byte[] buffer, int offset, int size) {
// Clamp reads past the end of the source.
if (position >= data.length) {
return -1; // -1 indicates EOF
}
if (position + size > data.length) {
size -= (position + size) - data.length;
}
System.arraycopy(data, (int) position, buffer, offset, size);
return size;
}
@Override
public synchronized long getSize() {
Log.v(TAG, "getSize: " + data.length);
return data.length;
}
// Note: it's fine to keep using this media item after closing it.
@Override
public synchronized void close() {
Log.v(TAG, "close()");
}
}
......@@ -20,60 +20,45 @@ import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.SessionPlayer.PlayerResult;
import androidx.media2.common.UriMediaItem;
import com.google.android.exoplayer2.ext.media2.test.R;
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
/** Utilities for tests. */
/* package */ final class TestUtils {
private static final long PLAYER_STATE_CHANGE_WAIT_TIME_MS = 5_000;
public static Uri createResourceUri(Context context, int resId) {
Resources resources = context.getResources();
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(resources.getResourcePackageName(resId))
.appendPath(resources.getResourceTypeName(resId))
.appendPath(resources.getResourceEntryName(resId))
.build();
}
public static MediaItem createMediaItem(Context context) {
return createMediaItem(context, R.raw.video_desks);
public static UriMediaItem createMediaItem() {
return createMediaItem(R.raw.video_desks);
}
public static MediaItem createMediaItem(Context context, int resId) {
Uri resourceUri = createResourceUri(context, resId);
String resourceName = context.getResources().getResourceName(resId);
public static UriMediaItem createMediaItem(int resId) {
MediaMetadata metadata =
new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, resourceName)
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Integer.toString(resId))
.build();
return new UriMediaItem.Builder(resourceUri).setMetadata(metadata).build();
return new UriMediaItem.Builder(RawResourceDataSource.buildRawResourceUri(resId))
.setMetadata(metadata)
.build();
}
public static List<MediaItem> createPlaylist(Context context, int size) {
public static List<MediaItem> createPlaylist(int size) {
List<MediaItem> items = new ArrayList<>();
for (int i = 0; i < size; ++i) {
items.add(createMediaItem(context));
items.add(createMediaItem());
}
return items;
}
public static void loadResource(Context context, int resId, SessionPlayer sessionPlayer)
throws Exception {
MediaItem mediaItem = createMediaItem(context, resId);
public static void loadResource(int resId, SessionPlayer sessionPlayer) throws Exception {
MediaItem mediaItem = createMediaItem(resId);
assertPlayerResultSuccess(sessionPlayer.setMediaItem(mediaItem));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment