diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b9252d1789f..9cb18be33db 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -13,6 +13,10 @@ * Text: * Gracefully handle null-terminated subtitle content in Matroska containers. +* Media2 extension + * Make media2-extension depend on AndroidX media2:media2-session:1.1.0 to + fix a deadlock while creating PlaybackStateCompat internally. + ([#8011](https://github.com/google/ExoPlayer/issues/8011)). ### 2.12.2 (2020-12-01) ### diff --git a/constants.gradle b/constants.gradle index b06636dd201..769f595b6d1 100644 --- a/constants.gradle +++ b/constants.gradle @@ -32,7 +32,9 @@ project.ext { androidxAnnotationVersion = '1.1.0' androidxAppCompatVersion = '1.1.0' androidxCollectionVersion = '1.1.0' + androidxFuturesVersion = '1.1.0' androidxMediaVersion = '1.0.1' + androidxMedia2Version = '1.1.0' androidxMultidexVersion = '2.0.0' androidxRecyclerViewVersion = '1.1.0' androidxTestCoreVersion = '1.2.0' diff --git a/extensions/media2/build.gradle b/extensions/media2/build.gradle index bdafee55585..a89354d7b34 100644 --- a/extensions/media2/build.gradle +++ b/extensions/media2/build.gradle @@ -18,8 +18,8 @@ android.defaultConfig.minSdkVersion 19 dependencies { implementation project(modulePrefix + 'library-core') implementation 'androidx.collection:collection:' + androidxCollectionVersion - implementation 'androidx.concurrent:concurrent-futures:1.1.0' - api 'androidx.media2:media2-session:1.0.3' + implementation 'androidx.concurrent:concurrent-futures:' + androidxFuturesVersion + api 'androidx.media2:media2-session:' + androidxMedia2Version compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion diff --git a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtilTest.java b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtilTest.java deleted file mode 100644 index 8cf586b8462..00000000000 --- a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtilTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 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 static com.google.common.truth.Truth.assertThat; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import android.content.Context; -import android.support.v4.media.session.MediaControllerCompat; -import android.support.v4.media.session.MediaSessionCompat; -import androidx.annotation.NonNull; -import androidx.media2.common.SessionPlayer; -import androidx.media2.common.SessionPlayer.PlayerResult; -import androidx.media2.session.MediaSession; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; -import com.google.android.exoplayer2.ext.media2.test.R; -import com.google.android.exoplayer2.util.Assertions; -import com.google.common.util.concurrent.ListenableFuture; -import java.util.concurrent.CountDownLatch; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Unit test for {@link MediaSessionUtil} */ -@RunWith(AndroidJUnit4.class) -public class MediaSessionUtilTest { - private static final int PLAYER_STATE_CHANGE_WAIT_TIME_MS = 5_000; - - @Rule public final PlayerTestRule playerTestRule = new PlayerTestRule(); - - @Test - public void getSessionCompatToken_withMediaControllerCompat_returnsValidToken() throws Exception { - Context context = ApplicationProvider.getApplicationContext(); - - SessionPlayerConnector sessionPlayerConnector = playerTestRule.getSessionPlayerConnector(); - MediaSession.SessionCallback sessionCallback = - new SessionCallbackBuilder(context, sessionPlayerConnector).build(); - TestUtils.loadResource(R.raw.audio, sessionPlayerConnector); - ListenableFuture prepareResult = sessionPlayerConnector.prepare(); - CountDownLatch latch = new CountDownLatch(1); - sessionPlayerConnector.registerPlayerCallback( - playerTestRule.getExecutor(), - new SessionPlayer.PlayerCallback() { - @Override - public void onPlayerStateChanged(@NonNull SessionPlayer player, int playerState) { - if (playerState == SessionPlayer.PLAYER_STATE_PLAYING) { - latch.countDown(); - } - } - }); - - MediaSession session2 = - new MediaSession.Builder(context, sessionPlayerConnector) - .setSessionCallback(playerTestRule.getExecutor(), sessionCallback) - .build(); - - InstrumentationRegistry.getInstrumentation() - .runOnMainSync( - () -> { - try { - MediaSessionCompat.Token token = - Assertions.checkNotNull(MediaSessionUtil.getSessionCompatToken(session2)); - MediaControllerCompat controllerCompat = new MediaControllerCompat(context, token); - controllerCompat.getTransportControls().play(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - }); - assertThat(prepareResult.get(PLAYER_STATE_CHANGE_WAIT_TIME_MS, MILLISECONDS).getResultCode()) - .isEqualTo(PlayerResult.RESULT_SUCCESS); - assertThat(latch.await(PLAYER_STATE_CHANGE_WAIT_TIME_MS, MILLISECONDS)).isTrue(); - } -} diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtil.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtil.java deleted file mode 100644 index e7cc9545b15..00000000000 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2020 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.annotation.SuppressLint; -import android.support.v4.media.session.MediaSessionCompat; -import androidx.media2.session.MediaSession; - -/** Utility methods to use {@link MediaSession} with other ExoPlayer modules. */ -public final class MediaSessionUtil { - - /** Gets the {@link MediaSessionCompat.Token} from the {@link MediaSession}. */ - // TODO(b/152764014): Deprecate this API when MediaSession#getSessionCompatToken() is released. - public static MediaSessionCompat.Token getSessionCompatToken(MediaSession mediaSession) { - @SuppressLint("RestrictedApi") - @SuppressWarnings("RestrictTo") - MediaSessionCompat sessionCompat = mediaSession.getSessionCompat(); - return sessionCompat.getSessionToken(); - } - - private MediaSessionUtil() { - // Prevent from instantiation. - } -} diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java index ccb29f8ca6b..0415a5cf384 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java @@ -437,8 +437,6 @@ public int getNextMediaItemIndex() { /* defaultValueWhenException= */ END_OF_PLAYLIST); } - // TODO(b/147706139): Call super.close() after updating media2-common to 1.1.0 - @SuppressWarnings("MissingSuperCall") @Override public void close() { synchronized (stateLock) { @@ -454,6 +452,7 @@ public void close() { player.close(); return null; }); + super.close(); } // SessionPlayerConnector-specific functions. @@ -559,8 +558,8 @@ private void notifySessionPlayerCallback(SessionPlayerCallbackNotifier notifier) } } - // TODO: Remove this suppress warnings and call onCurrentMediaItemChanged with a null item - // once AndroidX media2 1.2.0 is released + // TODO(internal b/160846312): Remove this suppress warnings and call onCurrentMediaItemChanged + // with a null item once we depend on media2 1.2.0. @SuppressWarnings("nullness:argument.type.incompatible") private void handlePlaylistChangedOnHandler() { List currentPlaylist = player.getPlaylist(); @@ -578,11 +577,6 @@ private void handlePlaylistChangedOnHandler() { SessionPlayerConnector.this, currentPlaylist, playlistMetadata); if (notifyCurrentMediaItem) { callback.onCurrentMediaItemChanged(SessionPlayerConnector.this, currentMediaItem); - - // Workaround for MediaSession's issue that current media item change isn't propagated - // to the legacy controllers. - // TODO(b/160846312): Remove this workaround with media2 1.1.0-stable. - callback.onSeekCompleted(SessionPlayerConnector.this, currentPosition); } }); } @@ -597,11 +591,6 @@ private void notifySkipToCompletedOnHandler() { notifySessionPlayerCallback( callback -> { callback.onCurrentMediaItemChanged(SessionPlayerConnector.this, currentMediaItem); - - // Workaround for MediaSession's issue that current media item change isn't propagated - // to the legacy controllers. - // TODO(b/160846312): Remove this workaround with media2 1.1.0-stable. - callback.onSeekCompleted(SessionPlayerConnector.this, currentPosition); }); } @@ -722,11 +711,6 @@ public void onCurrentMediaItemChanged(MediaItem mediaItem) { notifySessionPlayerCallback( callback -> { callback.onCurrentMediaItemChanged(SessionPlayerConnector.this, mediaItem); - - // Workaround for MediaSession's issue that current media item change isn't propagated - // to the legacy controllers. - // TODO(b/160846312): Remove this workaround with media2 1.1.0-stable. - callback.onSeekCompleted(SessionPlayerConnector.this, currentPosition); }); }