diff options
author | Gyumin Sim <gyumin@google.com> | 2019-06-26 06:52:20 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-06-26 06:52:20 +0000 |
commit | d79ec005cb6d0110827473b750bea235c655018a (patch) | |
tree | df95ed436629763779266d05e8ba81f6b1d7f093 | |
parent | 20cc23ee4769e48b6ae315d4901555402f282d71 (diff) | |
parent | efb401cf0da0fee8829a6e85d6f47fc6654333a4 (diff) | |
download | support-d79ec005cb6d0110827473b750bea235c655018a.tar.gz |
Merge "Test media2-widget with MediaController as well as SessionPlayer" into androidx-master-dev
8 files changed, 766 insertions, 457 deletions
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithControllerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithControllerTest.java new file mode 100644 index 00000000000..8d7b96fdac7 --- /dev/null +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithControllerTest.java @@ -0,0 +1,40 @@ +/* + * 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 androidx.media2.widget; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media2.common.MediaItem; +import androidx.media2.session.MediaController; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.runner.RunWith; + +/** + * Test {@link MediaControlView} with a {@link MediaController}. + * Please place actual test cases in {@link MediaControlView_WithSthTestBase}. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class MediaControlView_WithControllerTest extends MediaControlView_WithSthTestBase { + @Override + PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + return createPlayerWrapperOfController(callback, item); + } +} diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java index 5ea2a6b1a2e..10bc8741509 100644 --- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java @@ -16,299 +16,25 @@ package androidx.media2.widget; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup; -import static androidx.test.espresso.matcher.ViewMatchers.isClickable; -import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.net.Uri; - import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.media2.common.MediaItem; -import androidx.media2.common.MediaMetadata; import androidx.media2.common.SessionPlayer; -import androidx.media2.common.SessionPlayer.TrackInfo; -import androidx.media2.player.MediaPlayer; -import androidx.media2.widget.test.R; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - /** * Test {@link MediaControlView} with a {@link SessionPlayer}. + * Please place actual test cases in {@link MediaControlView_WithSthTestBase}. */ @RunWith(AndroidJUnit4.class) @LargeTest -public class MediaControlView_WithPlayerTest extends MediaWidgetTestBase { - private static final long FFWD_MS = 30000L; - private static final long REW_MS = 10000L; - - private SessionPlayer mPlayer; - private MediaControlViewTestActivity mActivity; - private MediaControlView mMediaControlView; - private MediaItem mFileSchemeMediaItem; - - @Rule - public ActivityTestRule<MediaControlViewTestActivity> mActivityRule = - new ActivityTestRule<>(MediaControlViewTestActivity.class); - - @Before - public void setup() throws Throwable { - mPlayer = new MediaPlayer(mContext); - mActivity = mActivityRule.getActivity(); - mMediaControlView = mActivity.findViewById(R.id.mediacontrolview); - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - mMediaControlView.setPlayer(mPlayer); - } - }); - - Uri fileSchemeUri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" - + R.raw.test_file_scheme_video); - mFileSchemeMediaItem = createTestMediaItem(fileSchemeUri); - - setKeepScreenOn(mActivityRule); - checkAttachedToWindow(mMediaControlView); - } - - @After - public void tearDown() throws Throwable { - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - mPlayer.close(); - } catch (Exception ex) { - // ignore - } - } - }); - } - - @Test - public void testPlayPauseButtonClick() throws Throwable { - final CountDownLatch latchForPausedState = new CountDownLatch(1); - final CountDownLatch latchForPlayingState = new CountDownLatch(1); - registerCallback(new SessionPlayer.PlayerCallback() { - @Override - public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) { - if (state == SessionPlayer.PLAYER_STATE_PAUSED) { - latchForPausedState.countDown(); - } else if (state == SessionPlayer.PLAYER_STATE_PLAYING) { - latchForPlayingState.countDown(); - } - } - }); - setAndPrepare(mFileSchemeMediaItem); - assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click()); - assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testFfwdButtonClick() throws Throwable { - final CountDownLatch latchForPausedState = new CountDownLatch(1); - final CountDownLatch latchForFfwd = new CountDownLatch(1); - registerCallback(new SessionPlayer.PlayerCallback() { - @Override - public void onSeekCompleted(@NonNull SessionPlayer player, long position) { - if (position >= FFWD_MS) { - latchForFfwd.countDown(); - } - } - - @Override - public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) { - if (state == SessionPlayer.PLAYER_STATE_PAUSED) { - latchForPausedState.countDown(); - } - } - }); - setAndPrepare(mFileSchemeMediaItem); - assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - onView(allOf(withId(R.id.ffwd), isCompletelyDisplayed())).perform(click()); - assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testRewButtonClick() throws Throwable { - final CountDownLatch latchForFfwd = new CountDownLatch(1); - final CountDownLatch latchForRew = new CountDownLatch(1); - registerCallback(new SessionPlayer.PlayerCallback() { - long mExpectedPosition = FFWD_MS; - final long mDelta = 1000L; - - @Override - public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) { - if (state == SessionPlayer.PLAYER_STATE_PAUSED) { - mExpectedPosition = FFWD_MS; - player.seekTo(mExpectedPosition); - } - } - - @Override - public void onSeekCompleted(@NonNull SessionPlayer player, long position) { - // Ignore the initial seek. Internal MediaPlayer behavior can be changed. - if (position == 0 && mExpectedPosition == FFWD_MS) { - return; - } - assertTrue(equalsSeekPosition(mExpectedPosition, position, mDelta)); - if (mExpectedPosition == FFWD_MS) { - mExpectedPosition = position - REW_MS; - latchForFfwd.countDown(); - } else { - latchForRew.countDown(); - } - } - - private boolean equalsSeekPosition(long expected, long actual, long delta) { - return (actual < expected + delta) && (actual > expected - delta); - } - }); - setAndPrepare(mFileSchemeMediaItem); - assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - onView(allOf(withId(R.id.rew), isCompletelyDisplayed())).perform(click()); - assertTrue(latchForRew.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testSetMetadataForNonMusicFile() throws Throwable { - final String title = "BigBuckBunny"; - final CountDownLatch latch = new CountDownLatch(1); - final MediaMetadata metadata = new MediaMetadata.Builder() - .putString(MediaMetadata.METADATA_KEY_TITLE, title).build(); - registerCallback(new SessionPlayer.PlayerCallback() { - @Override - public void onCurrentMediaItemChanged(@NonNull SessionPlayer player, - @NonNull MediaItem item) { - assertNotNull(item); - assertNotNull(item.getMetadata()); - assertEquals(title, metadata.getString(MediaMetadata.METADATA_KEY_TITLE)); - latch.countDown(); - } - }); - mFileSchemeMediaItem.setMetadata(metadata); - mPlayer.setMediaItem(mFileSchemeMediaItem); - assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - onView(withId(R.id.title_text)).check(matches(withText(title))); - } - - @Test - public void testButtonVisibilityForMusicFile() throws Throwable { - Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" - + R.raw.test_music); - final MediaItem uriMediaItem = createTestMediaItem(uri); - - final CountDownLatch latch = new CountDownLatch(1); - registerCallback(new SessionPlayer.PlayerCallback() { - @Override - public void onTrackInfoChanged(@NonNull SessionPlayer player, - @NonNull List<TrackInfo> trackInfos) { - latch.countDown(); - } - }); - setAndPrepare(uriMediaItem); - assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - onView(withId(R.id.subtitle)).check(matches(not(isDisplayed()))); - } - - @Test - public void testUpdateAndSelectSubtitleTrack() throws Throwable { - Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" - + R.raw.testvideo_with_2_subtitle_tracks); - - final String subtitleTrackOffText = mContext.getResources().getString( - R.string.MediaControlView_subtitle_off_text); - final String subtitleTrack1Text = mContext.getResources().getString( - R.string.MediaControlView_subtitle_track_number_text, 1); - - final MediaItem mediaItem = createTestMediaItem(uri); - - final CountDownLatch latchForTrackUpdate = new CountDownLatch(1); - final CountDownLatch latchForSubtitleSelect = new CountDownLatch(1); - final CountDownLatch latchForSubtitleDeselect = new CountDownLatch(1); - registerCallback(new SessionPlayer.PlayerCallback() { - private TrackInfo mFirstSubtitleTrack; - - @Override - public void onTrackInfoChanged(@NonNull SessionPlayer player, - @NonNull List<TrackInfo> trackInfos) { - if (mFirstSubtitleTrack != null) { - return; - } - assertNotNull(trackInfos); - for (int i = 0; i < trackInfos.size(); i++) { - TrackInfo trackInfo = trackInfos.get(i); - if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { - mFirstSubtitleTrack = trackInfo; - latchForTrackUpdate.countDown(); - break; - } - } - } - - @Override - public void onTrackSelected(@NonNull SessionPlayer player, - @NonNull TrackInfo trackInfo) { - assertEquals(mFirstSubtitleTrack, trackInfo); - latchForSubtitleSelect.countDown(); - } - - @Override - public void onTrackDeselected(@NonNull SessionPlayer player, - @NonNull TrackInfo trackInfo) { - assertEquals(mFirstSubtitleTrack, trackInfo); - latchForSubtitleDeselect.countDown(); - } - }); - // MediaPlayer needs a surface to be set in order to produce subtitle tracks - mPlayer.setSurfaceInternal(mActivity.getSurfaceHolder().getSurface()); - setAndPrepare(mediaItem); - assertTrue(latchForTrackUpdate.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - - onView(withId(R.id.subtitle)).check(matches(isClickable())); - onView(withId(R.id.subtitle)).perform(click()); - onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup()) - .check(matches(isCompletelyDisplayed())); - onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup()).perform(click()); - assertTrue(latchForSubtitleSelect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - - onView(withId(R.id.subtitle)).check(matches(isClickable())); - onView(withId(R.id.subtitle)).perform(click()); - onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup()) - .check(matches(isCompletelyDisplayed())); - onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup()).perform(click()); - assertTrue(latchForSubtitleDeselect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - } - - private void registerCallback(SessionPlayer.PlayerCallback callback) { - mPlayer.registerPlayerCallback(mMainHandlerExecutor, callback); - } - - private void setAndPrepare(MediaItem item) { - mPlayer.setMediaItem(item); - mPlayer.prepare(); +public class MediaControlView_WithPlayerTest extends MediaControlView_WithSthTestBase { + @Override + PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + return createPlayerWrapperOfPlayer(callback, item); } } diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithSthTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithSthTestBase.java new file mode 100644 index 00000000000..5ad0eb6a218 --- /dev/null +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithSthTestBase.java @@ -0,0 +1,316 @@ +/* + * 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 androidx.media2.widget; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup; +import static androidx.test.espresso.matcher.ViewMatchers.isClickable; +import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media2.common.MediaItem; +import androidx.media2.common.MediaMetadata; +import androidx.media2.common.SessionPlayer; +import androidx.media2.common.SessionPlayer.TrackInfo; +import androidx.media2.session.MediaController; +import androidx.media2.widget.test.R; +import androidx.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Base class for testing {@link MediaControlView} with a {@link SessionPlayer} or + * {@link MediaController}. + */ +public abstract class MediaControlView_WithSthTestBase extends MediaWidgetTestBase { + private static final long FFWD_MS = 30000L; + private static final long REW_MS = 10000L; + + private MediaControlViewTestActivity mActivity; + private MediaControlView mMediaControlView; + private MediaItem mFileSchemeMediaItem; + + @Rule + public ActivityTestRule<MediaControlViewTestActivity> mActivityRule = + new ActivityTestRule<>(MediaControlViewTestActivity.class); + + @Before + public void setup() throws Throwable { + mActivity = mActivityRule.getActivity(); + mMediaControlView = mActivity.findViewById(R.id.mediacontrolview); + + Uri fileSchemeUri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + + R.raw.test_file_scheme_video); + mFileSchemeMediaItem = createTestMediaItem(fileSchemeUri); + + setKeepScreenOn(mActivityRule); + checkAttachedToWindow(mMediaControlView); + } + + @After + public void tearDown() throws Throwable { + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + closeAll(); + } + }); + } + + @Test + public void testPlayPauseButtonClick() throws Throwable { + final CountDownLatch latchForPausedState = new CountDownLatch(1); + final CountDownLatch latchForPlayingState = new CountDownLatch(1); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + @Override + public void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) { + if (state == SessionPlayer.PLAYER_STATE_PAUSED) { + latchForPausedState.countDown(); + } else if (state == SessionPlayer.PLAYER_STATE_PLAYING) { + latchForPlayingState.countDown(); + } + } + }, mFileSchemeMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + onView(allOf(withId(R.id.pause), isCompletelyDisplayed())).perform(click()); + assertTrue(latchForPlayingState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testFfwdButtonClick() throws Throwable { + final CountDownLatch latchForPausedState = new CountDownLatch(1); + final CountDownLatch latchForFfwd = new CountDownLatch(1); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + @Override + public void onSeekCompleted(@NonNull PlayerWrapper player, long position) { + if (position >= FFWD_MS) { + latchForFfwd.countDown(); + } + } + + @Override + public void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) { + if (state == SessionPlayer.PLAYER_STATE_PAUSED) { + latchForPausedState.countDown(); + } + } + }, mFileSchemeMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latchForPausedState.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + onView(allOf(withId(R.id.ffwd), isCompletelyDisplayed())).perform(click()); + assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testRewButtonClick() throws Throwable { + final CountDownLatch latchForFfwd = new CountDownLatch(1); + final CountDownLatch latchForRew = new CountDownLatch(1); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + long mExpectedPosition = FFWD_MS; + final long mDelta = 1000L; + + @Override + public void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) { + if (state == SessionPlayer.PLAYER_STATE_PAUSED) { + mExpectedPosition = FFWD_MS; + player.seekTo(mExpectedPosition); + } + } + + @Override + public void onSeekCompleted(@NonNull PlayerWrapper player, long position) { + // Ignore the initial seek. Internal MediaPlayer behavior can be changed. + if (position == 0 && mExpectedPosition == FFWD_MS) { + return; + } + assertTrue(equalsSeekPosition(mExpectedPosition, position, mDelta)); + if (mExpectedPosition == FFWD_MS) { + mExpectedPosition = position - REW_MS; + latchForFfwd.countDown(); + } else { + latchForRew.countDown(); + } + } + + private boolean equalsSeekPosition(long expected, long actual, long delta) { + return (actual < expected + delta) && (actual > expected - delta); + } + }, mFileSchemeMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latchForFfwd.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + onView(allOf(withId(R.id.rew), isCompletelyDisplayed())).perform(click()); + assertTrue(latchForRew.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSetMetadataForNonMusicFile() throws Throwable { + final String title = "BigBuckBunny"; + final CountDownLatch latch = new CountDownLatch(1); + final MediaMetadata metadata = new MediaMetadata.Builder() + .putString(MediaMetadata.METADATA_KEY_TITLE, title).build(); + mFileSchemeMediaItem.setMetadata(metadata); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + @Override + public void onCurrentMediaItemChanged(@NonNull PlayerWrapper player, + @Nullable MediaItem item) { + if (item != null) { + assertNotNull(item.getMetadata()); + assertEquals(title, metadata.getString(MediaMetadata.METADATA_KEY_TITLE)); + latch.countDown(); + } + } + }, mFileSchemeMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + onView(withId(R.id.title_text)).check(matches(withText(title))); + } + + @Test + public void testButtonVisibilityForMusicFile() throws Throwable { + Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + + R.raw.test_music); + final MediaItem uriMediaItem = createTestMediaItem(uri); + + final CountDownLatch latch = new CountDownLatch(1); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + @Override + public void onTrackInfoChanged(@NonNull PlayerWrapper player, + @NonNull List<TrackInfo> trackInfos) { + latch.countDown(); + } + }, uriMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + onView(withId(R.id.subtitle)).check(matches(not(isDisplayed()))); + } + + @Test + public void testUpdateAndSelectSubtitleTrack() throws Throwable { + Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + + R.raw.testvideo_with_2_subtitle_tracks); + + final String subtitleTrackOffText = mContext.getResources().getString( + R.string.MediaControlView_subtitle_off_text); + final String subtitleTrack1Text = mContext.getResources().getString( + R.string.MediaControlView_subtitle_track_number_text, 1); + + final MediaItem mediaItem = createTestMediaItem(uri); + + final CountDownLatch latchForReady = new CountDownLatch(1); + final CountDownLatch latchForTrackUpdate = new CountDownLatch(1); + final CountDownLatch latchForSubtitleSelect = new CountDownLatch(1); + final CountDownLatch latchForSubtitleDeselect = new CountDownLatch(1); + final PlayerWrapper playerWrapper = createPlayerWrapper(new PlayerWrapper.PlayerCallback() { + private TrackInfo mFirstSubtitleTrack; + + @Override + public void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) { + if (state == SessionPlayer.PLAYER_STATE_PAUSED) { + latchForReady.countDown(); + } + } + + @Override + public void onTrackInfoChanged(@NonNull PlayerWrapper player, + @NonNull List<TrackInfo> trackInfos) { + if (mFirstSubtitleTrack != null) { + return; + } + assertNotNull(trackInfos); + for (int i = 0; i < trackInfos.size(); i++) { + TrackInfo trackInfo = trackInfos.get(i); + if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { + mFirstSubtitleTrack = trackInfo; + latchForTrackUpdate.countDown(); + break; + } + } + } + + @Override + public void onTrackSelected(@NonNull PlayerWrapper player, + @NonNull TrackInfo trackInfo) { + assertEquals(mFirstSubtitleTrack, trackInfo); + latchForSubtitleSelect.countDown(); + } + + @Override + public void onTrackDeselected(@NonNull PlayerWrapper player, + @NonNull TrackInfo trackInfo) { + assertEquals(mFirstSubtitleTrack, trackInfo); + latchForSubtitleDeselect.countDown(); + } + }, mediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(latchForReady.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + // MediaPlayer needs a surface to be set in order to produce subtitle tracks + playerWrapper.setSurface(mActivity.getSurfaceHolder().getSurface()); + assertTrue(latchForTrackUpdate.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + + onView(withId(R.id.subtitle)).check(matches(isClickable())); + onView(withId(R.id.subtitle)).perform(click()); + onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup()) + .check(matches(isCompletelyDisplayed())); + onView(withText(subtitleTrack1Text)).inRoot(isPlatformPopup()).perform(click()); + assertTrue(latchForSubtitleSelect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + + onView(withId(R.id.subtitle)).check(matches(isClickable())); + onView(withId(R.id.subtitle)).perform(click()); + onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup()) + .check(matches(isCompletelyDisplayed())); + onView(withText(subtitleTrackOffText)).inRoot(isPlatformPopup()).perform(click()); + assertTrue(latchForSubtitleDeselect.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + private void setPlayerWrapper(final PlayerWrapper playerWrapper) throws Throwable { + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + if (playerWrapper.mPlayer != null) { + mMediaControlView.setPlayer(playerWrapper.mPlayer); + } else if (playerWrapper.mController != null) { + mMediaControlView.setMediaController(playerWrapper.mController); + } + } + }); + } + + abstract PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item); +} diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java new file mode 100644 index 00000000000..e6d429f41c0 --- /dev/null +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaTestBase.java @@ -0,0 +1,70 @@ +/* + * 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 androidx.media2.widget; + +import android.content.Context; +import android.media.AudioManager; +import android.os.Looper; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.BeforeClass; + +/** + * Base class for all media tests. + */ +abstract class MediaTestBase { + /** + * All tests methods should start with this. + * <p> + * MediaControllerCompat, which is wrapped by the MediaSession, can be only created by the + * thread whose Looper is prepared. However, when the presubmit test runs on the server, + * test runs with the {@link org.junit.internal.runners.statements.FailOnTimeout} which creates + * dedicated thread for running test methods while methods annotated with @After or @Before + * runs on the normal test different thread. This ensures that the current Looper is prepared. + */ + public static void prepareLooper() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + } + + @BeforeClass + public static void setupMainLooper() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + // Prepare the main looper if it hasn't. + // Some framework APIs always run on the main looper. + if (Looper.getMainLooper() == null) { + Looper.prepareMainLooper(); + } + + // Initialize AudioManager on the main thread to workaround b/78617702 that + // audio focus listener is called on the thread where the AudioManager was + // originally initialized. + // 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. + Context context = ApplicationProvider.getApplicationContext(); + AudioManager manager = + (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + } + }); + } +} diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java index e93dced91e1..c6960d32056 100644 --- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java @@ -29,9 +29,15 @@ import android.os.Build; import android.view.View; import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.media2.common.MediaItem; +import androidx.media2.common.SessionPlayer; import androidx.media2.common.UriMediaItem; +import androidx.media2.player.MediaPlayer; +import androidx.media2.session.MediaController; +import androidx.media2.session.MediaSession; import androidx.media2.widget.test.R; import androidx.test.core.app.ApplicationProvider; import androidx.test.platform.app.InstrumentationRegistry; @@ -39,14 +45,20 @@ import androidx.test.rule.ActivityTestRule; import org.junit.Before; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -public class MediaWidgetTestBase { +public class MediaWidgetTestBase extends MediaTestBase { // Expected success time static final int WAIT_TIME_MS = 1000; + private List<SessionPlayer> mPlayers = new ArrayList<>(); + private List<MediaSession> mSessions = new ArrayList<>(); + private List<MediaController> mControllers = new ArrayList<>(); + Context mContext; Executor mMainHandlerExecutor; @@ -108,4 +120,57 @@ public class MediaWidgetTestBase { MediaItem createTestMediaItem(Uri uri) { return new UriMediaItem.Builder(uri).build(); } + + PlayerWrapper createPlayerWrapperOfController(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + prepareLooper(); + + SessionPlayer player = new MediaPlayer(mContext); + MediaSession session = new MediaSession.Builder(mContext, player).build(); + MediaController controller = new MediaController.Builder(mContext) + .setSessionToken(session.getToken()) + .build(); + mPlayers.add(player); + mSessions.add(session); + mControllers.add(controller); + PlayerWrapper wrapper = new PlayerWrapper(controller, mMainHandlerExecutor, callback); + wrapper.attachCallback(); + if (item != null) { + player.setMediaItem(item); + player.prepare(); + } + return wrapper; + } + + PlayerWrapper createPlayerWrapperOfPlayer(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + SessionPlayer player = new MediaPlayer(mContext); + mPlayers.add(player); + PlayerWrapper wrapper = new PlayerWrapper(player, mMainHandlerExecutor, callback); + wrapper.attachCallback(); + if (item != null) { + player.setMediaItem(item); + player.prepare(); + } + return wrapper; + } + + void closeAll() { + for (MediaController controller : mControllers) { + controller.close(); + } + for (MediaSession session : mSessions) { + session.close(); + } + for (SessionPlayer player : mPlayers) { + try { + player.close(); + } catch (Exception ex) { + // ignore + } + } + mControllers.clear(); + mSessions.clear(); + mPlayers.clear(); + } } diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithControllerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithControllerTest.java new file mode 100644 index 00000000000..8fd72f56141 --- /dev/null +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithControllerTest.java @@ -0,0 +1,39 @@ +/* + * 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 androidx.media2.widget; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media2.common.MediaItem; +import androidx.media2.session.MediaController; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.runner.RunWith; + +/** + * Test {@link VideoView} with a {@link MediaController}. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class VideoView_WithControllerTest extends VideoView_WithSthTestBase { + @Override + PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + return createPlayerWrapperOfController(callback, item); + } +} diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java index 1fa4b5c8888..9387a4197a1 100644 --- a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java @@ -16,31 +16,13 @@ package androidx.media2.widget; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.after; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -import android.app.Activity; -import android.content.res.AssetFileDescriptor; -import android.os.ParcelFileDescriptor; - -import androidx.media2.common.FileMediaItem; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.media2.common.MediaItem; import androidx.media2.common.SessionPlayer; -import androidx.media2.player.MediaPlayer; -import androidx.media2.widget.test.R; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; import org.junit.runner.RunWith; /** @@ -48,160 +30,10 @@ import org.junit.runner.RunWith; */ @RunWith(AndroidJUnit4.class) @LargeTest -public class VideoView_WithPlayerTest extends MediaWidgetTestBase { - private Activity mActivity; - private VideoView mVideoView; - private MediaItem mMediaItem; - private SessionPlayer.PlayerCallback mPlayerCallback; - private SessionPlayer mPlayer; - - @Rule - public ActivityTestRule<VideoViewTestActivity> mActivityRule = - new ActivityTestRule<>(VideoViewTestActivity.class); - - @Before - public void setup() throws Throwable { - mActivity = mActivityRule.getActivity(); - mVideoView = mActivity.findViewById(R.id.videoview); - mMediaItem = createTestMediaItem(); - - setKeepScreenOn(mActivityRule); - checkAttachedToWindow(mVideoView); - - mPlayerCallback = mock(SessionPlayer.PlayerCallback.class); - mPlayer = new MediaPlayer(mContext); - mPlayer.registerPlayerCallback(mMainHandlerExecutor, mPlayerCallback); - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - mVideoView.setPlayer(mPlayer); - } - }); - } - - @After - public void tearDown() throws Throwable { - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - mPlayer.close(); - } catch (Exception ex) { - // ignore - } - } - }); - } - - @Test - public void testPlayVideo() throws Throwable { - waitToPrepare(mMediaItem); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onCurrentMediaItemChanged( - any(SessionPlayer.class), any(MediaItem.class)); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PAUSED)); - verify(mPlayerCallback, after(WAIT_TIME_MS).never()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PLAYING)); - assertEquals(SessionPlayer.PLAYER_STATE_PAUSED, mPlayer.getPlayerState()); - - mPlayer.play(); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PLAYING)); - } - - @Test - public void testPlayVideoWithMediaItemFromFileDescriptor() throws Throwable { - AssetFileDescriptor afd = mContext.getResources() - .openRawResourceFd(R.raw.testvideo_with_2_subtitle_tracks); - final MediaItem item = new FileMediaItem.Builder( - ParcelFileDescriptor.dup(afd.getFileDescriptor())) - .setFileDescriptorOffset(afd.getStartOffset()) - .setFileDescriptorLength(afd.getLength()) - .build(); - afd.close(); - - waitToPrepare(item); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onCurrentMediaItemChanged( - any(SessionPlayer.class), eq(item)); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PAUSED)); - - mPlayer.play(); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PLAYING)); - } - - @Test - public void testPlayVideoOnTextureView() throws Throwable { - final VideoView.OnViewTypeChangedListener mockViewTypeListener = - mock(VideoView.OnViewTypeChangedListener.class); - - // The default view type is surface view. - assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); - - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - mVideoView.setOnViewTypeChangedListener(mockViewTypeListener); - mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); - } - }); - waitToPrepare(mMediaItem); - verify(mockViewTypeListener, timeout(WAIT_TIME_MS)) - .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onCurrentMediaItemChanged( - any(SessionPlayer.class), any(MediaItem.class)); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeast(1)).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PAUSED)); - - mPlayer.play(); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeast(1)).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PLAYING)); - } - - @Test - public void testSetViewType() throws Throwable { - final VideoView.OnViewTypeChangedListener mockViewTypeListener = - mock(VideoView.OnViewTypeChangedListener.class); - - // The default view type is surface view. - assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); - - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - mVideoView.setOnViewTypeChangedListener(mockViewTypeListener); - mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); - mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW); - mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); - mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW); - } - }); - - waitToPrepare(mMediaItem); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onCurrentMediaItemChanged( - any(SessionPlayer.class), any(MediaItem.class)); - // WAIT_TIME_MS multiplied by the number of operations. - verify(mPlayerCallback, timeout(WAIT_TIME_MS * 5).atLeast(1)).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PAUSED)); - assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); - - mPlayer.play(); - verify(mPlayerCallback, timeout(WAIT_TIME_MS).atLeastOnce()).onPlayerStateChanged( - any(SessionPlayer.class), eq(SessionPlayer.PLAYER_STATE_PLAYING)); - - mActivityRule.runOnUiThread(new Runnable() { - @Override - public void run() { - mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); - } - }); - verify(mockViewTypeListener, timeout(WAIT_TIME_MS)) - .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW); - } - - private void waitToPrepare(MediaItem item) throws Exception { - mPlayer.setMediaItem(item); - mPlayer.prepare().get(); +public class VideoView_WithPlayerTest extends VideoView_WithSthTestBase { + @Override + PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item) { + return createPlayerWrapperOfPlayer(callback, item); } } diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithSthTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithSthTestBase.java new file mode 100644 index 00000000000..1996d0d5820 --- /dev/null +++ b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithSthTestBase.java @@ -0,0 +1,221 @@ +/* + * 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 androidx.media2.widget; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.content.res.AssetFileDescriptor; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media2.common.FileMediaItem; +import androidx.media2.common.MediaItem; +import androidx.media2.common.SessionPlayer; +import androidx.media2.session.MediaController; +import androidx.media2.widget.test.R; +import androidx.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Base class for testing {@link VideoView} with a {@link SessionPlayer} or + * {@link MediaController}. + */ +public abstract class VideoView_WithSthTestBase extends MediaWidgetTestBase { + private Activity mActivity; + private VideoView mVideoView; + private MediaItem mMediaItem; + + @Rule + public ActivityTestRule<VideoViewTestActivity> mActivityRule = + new ActivityTestRule<>(VideoViewTestActivity.class); + + @Before + public void setup() throws Throwable { + mActivity = mActivityRule.getActivity(); + mVideoView = mActivity.findViewById(R.id.videoview); + mMediaItem = createTestMediaItem(); + + setKeepScreenOn(mActivityRule); + checkAttachedToWindow(mVideoView); + } + + @After + public void tearDown() throws Throwable { + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + closeAll(); + } + }); + } + + @Test + public void testPlayVideo() throws Throwable { + PlayerCallback callback = new PlayerCallback(); + PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem); + setPlayerWrapper(playerWrapper); + assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + assertEquals(1, callback.mPlayingLatch.getCount()); + assertEquals(SessionPlayer.PLAYER_STATE_PAUSED, playerWrapper.getPlayerState()); + + playerWrapper.play(); + assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testPlayVideoWithMediaItemFromFileDescriptor() throws Throwable { + AssetFileDescriptor afd = mContext.getResources() + .openRawResourceFd(R.raw.testvideo_with_2_subtitle_tracks); + final MediaItem item = new FileMediaItem.Builder( + ParcelFileDescriptor.dup(afd.getFileDescriptor())) + .setFileDescriptorOffset(afd.getStartOffset()) + .setFileDescriptorLength(afd.getLength()) + .build(); + afd.close(); + + PlayerCallback callback = new PlayerCallback(); + PlayerWrapper playerWrapper = createPlayerWrapper(callback, item); + setPlayerWrapper(playerWrapper); + assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + + playerWrapper.play(); + assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testPlayVideoOnTextureView() throws Throwable { + final VideoView.OnViewTypeChangedListener mockViewTypeListener = + mock(VideoView.OnViewTypeChangedListener.class); + + PlayerCallback callback = new PlayerCallback(); + PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem); + setPlayerWrapper(playerWrapper); + + // The default view type is surface view. + assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); + + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mVideoView.setOnViewTypeChangedListener(mockViewTypeListener); + mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); + } + }); + verify(mockViewTypeListener, timeout(WAIT_TIME_MS)) + .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW); + assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + + playerWrapper.play(); + assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testSetViewType() throws Throwable { + final VideoView.OnViewTypeChangedListener mockViewTypeListener = + mock(VideoView.OnViewTypeChangedListener.class); + + PlayerCallback callback = new PlayerCallback(); + PlayerWrapper playerWrapper = createPlayerWrapper(callback, mMediaItem); + setPlayerWrapper(playerWrapper); + + // The default view type is surface view. + assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); + + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mVideoView.setOnViewTypeChangedListener(mockViewTypeListener); + mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); + mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW); + mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); + mVideoView.setViewType(VideoView.VIEW_TYPE_SURFACEVIEW); + } + }); + + assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + // WAIT_TIME_MS multiplied by the number of operations. + assertTrue(callback.mPausedLatch.await(WAIT_TIME_MS * 5, TimeUnit.MILLISECONDS)); + assertEquals(mVideoView.getViewType(), VideoView.VIEW_TYPE_SURFACEVIEW); + + playerWrapper.play(); + assertTrue(callback.mPlayingLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mVideoView.setViewType(VideoView.VIEW_TYPE_TEXTUREVIEW); + } + }); + verify(mockViewTypeListener, timeout(WAIT_TIME_MS)) + .onViewTypeChanged(mVideoView, VideoView.VIEW_TYPE_TEXTUREVIEW); + } + + private void setPlayerWrapper(final PlayerWrapper playerWrapper) throws Throwable { + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + if (playerWrapper.mPlayer != null) { + mVideoView.setPlayer(playerWrapper.mPlayer); + } else if (playerWrapper.mController != null) { + mVideoView.setMediaController(playerWrapper.mController); + } + } + }); + } + + private class PlayerCallback extends PlayerWrapper.PlayerCallback { + private CountDownLatch mItemLatch = new CountDownLatch(1); + private CountDownLatch mPausedLatch = new CountDownLatch(1); + private CountDownLatch mPlayingLatch = new CountDownLatch(1); + + @Override + void onCurrentMediaItemChanged(@NonNull PlayerWrapper player, + @Nullable MediaItem item) { + if (item != null) { + mItemLatch.countDown(); + } + } + + @Override + void onPlayerStateChanged(@NonNull PlayerWrapper player, int state) { + if (state == SessionPlayer.PLAYER_STATE_PAUSED) { + mPausedLatch.countDown(); + } else if (state == SessionPlayer.PLAYER_STATE_PLAYING) { + mPlayingLatch.countDown(); + } + } + } + + abstract PlayerWrapper createPlayerWrapper(@NonNull PlayerWrapper.PlayerCallback callback, + @Nullable MediaItem item); +} |