diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7c091be56b5..fdb2b72475d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -60,6 +60,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) ### @@ -121,6 +125,7 @@ * Notify onBufferingEnded when the state of origin player becomes `STATE_IDLE` or `STATE_ENDED`. * Allow to remove all playlist items that makes the player reset. + ([#8047](https://github.com/google/ExoPlayer/issues/8047)). * MediaSession extension: * Support `setPlaybackSpeed(float)` and disable it by default. Use `MediaSessionConnector.setEnabledPlaybackActions(long)` to enable diff --git a/constants.gradle b/constants.gradle index cbac0790318..ce1b9b8e34c 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.2.1' + androidxMedia2Version = '1.1.0' androidxMultidexVersion = '2.0.0' androidxRecyclerViewVersion = '1.1.0' androidxTestCoreVersion = '1.3.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 33667badef2..00000000000 --- a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/MediaSessionUtilTest.java +++ /dev/null @@ -1,87 +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.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(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 510759afb73..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(internal b/160846312): Remove lint/warning suppression once we depend on media2 1.1.0. - 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 dd2893c5eae..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,9 +437,7 @@ public int getNextMediaItemIndex() { /* defaultValueWhenException= */ END_OF_PLAYLIST); } - // TODO(internal b/160846312): Call super.close() once we depend on media2 1.1.0. @Override - @SuppressWarnings("MissingSuperCall") public void close() { synchronized (stateLock) { if (closed) { @@ -454,6 +452,7 @@ public void close() { player.close(); return null; }); + super.close(); } // SessionPlayerConnector-specific functions. @@ -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(internal b/160846312): Remove workaround once we depend on media2 1.1.0. - 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(internal b/160846312): Remove workaround once we depend on media2 1.1.0. - 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(internal b/160846312): Remove workaround once we depend on media2 1.1.0. - callback.onSeekCompleted(SessionPlayerConnector.this, currentPosition); }); }