diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 04611b014e..05f65585c6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -40,6 +40,9 @@ in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`. * IMA extension: * Session: + * Fix bug where calling a `Player` method on a `MediaController` connected + to a legacy session dropped changes of a pending update coming from the + legacy session. * UI: * Add `PresentationState` state holder class and the corresponding `rememberPresentationState` Composable to `media3-ui-compose`. diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index c32362c439..fcd454217f 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -1593,6 +1593,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; updateControllerInfo( notifyConnected, newLegacyPlayerInfo, + /* resetPendingLegacyPlayerInfo= */ true, newControllerInfo, /* discontinuityReason= */ reasons.first, /* mediaItemTransitionReason= */ reasons.second); @@ -1616,6 +1617,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; updateControllerInfo( /* notifyConnected= */ false, legacyPlayerInfo, + /* resetPendingLegacyPlayerInfo= */ false, newControllerInfo, discontinuityReason, mediaItemTransitionReason); @@ -1626,6 +1628,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; private void updateControllerInfo( boolean notifyConnected, LegacyPlayerInfo newLegacyPlayerInfo, + boolean resetPendingLegacyPlayerInfo, ControllerInfo newControllerInfo, @Nullable @Player.DiscontinuityReason Integer discontinuityReason, @Nullable @Player.MediaItemTransitionReason Integer mediaItemTransitionReason) { @@ -1634,7 +1637,9 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; if (legacyPlayerInfo != newLegacyPlayerInfo) { legacyPlayerInfo = new LegacyPlayerInfo(newLegacyPlayerInfo); } - pendingLegacyPlayerInfo = legacyPlayerInfo; + if (resetPendingLegacyPlayerInfo) { + pendingLegacyPlayerInfo = legacyPlayerInfo; + } controllerInfo = newControllerInfo; if (notifyConnected) { diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java index c453b2ddd0..2fdc8254ff 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java @@ -38,6 +38,7 @@ import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.Util; +import androidx.media3.session.legacy.MediaMetadataCompat; import androidx.media3.test.session.R; import androidx.media3.test.session.common.CommonConstants; import androidx.media3.test.session.common.HandlerThreadTestRule; @@ -818,4 +819,51 @@ public class MediaControllerListenerWithMediaSessionCompatTest { assertThat(threadTestRule.getHandler().postAndSync(controller::getCurrentPosition)) .isEqualTo(0); } + + @SuppressWarnings("deprecation") // Testing interoperability and backwards compatibility. + @Test + public void setDeviceVolume_whenWaitingForPendingUpdates_maskingDoesNotOverridePendingUpdate() + throws Exception { + MediaController controller = controllerTestRule.createController(session.getSessionToken()); + List reportedPlaybackStates = new ArrayList<>(); + List reportedMediaMetadata = new ArrayList<>(); + ConditionVariable playbackStateChanged = new ConditionVariable(); + ConditionVariable mediaMetadataChanged = new ConditionVariable(); + playbackStateChanged.close(); + controller.addListener( + new Player.Listener() { + @Override + public void onPlaybackStateChanged(int playbackState) { + reportedPlaybackStates.add(playbackState); + playbackStateChanged.open(); + } + + @Override + public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { + reportedMediaMetadata.add(mediaMetadata); + mediaMetadataChanged.open(); + } + }); + + session.setMetadata( + new android.support.v4.media.MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "artist-0") + .build()); + session.setPlaybackState( + new PlaybackStateCompat.Builder() + .setState( + PlaybackStateCompat.STATE_PLAYING, /* position= */ 1001L, /* playbackSpeed= */ 1.0f) + .build()); + synchronized (this) { + // Wait 200ms to make playback state and metadata arrive. + Thread.sleep(200); + // Trigger masking than must not drop the pending legacy info. + threadTestRule.getHandler().postAndSync(() -> controller.setDeviceVolume(1, 0)); + } + + assertThat(playbackStateChanged.block(TIMEOUT_MS)).isTrue(); + assertThat(mediaMetadataChanged.block(TIMEOUT_MS)).isTrue(); + assertThat(reportedPlaybackStates).containsExactly(3); + assertThat(reportedMediaMetadata.stream().map((m) -> m.artist)).containsExactly("artist-0"); + } }