diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java index 1dae1d41f3b..d07801ea2a1 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java @@ -938,20 +938,23 @@ protected MediaSessionServiceLegacyStub getLegacyBrowserService() { }); } - /* package */ boolean onPlayRequested() { + /* package */ ListenableFuture<Boolean> onPlayRequested() { if (Looper.myLooper() != Looper.getMainLooper()) { SettableFuture<Boolean> playRequested = SettableFuture.create(); - mainHandler.post(() -> playRequested.set(onPlayRequested())); - try { - return playRequested.get(); - } catch (InterruptedException | ExecutionException e) { - throw new IllegalStateException(e); - } + mainHandler.post( + () -> { + try { + playRequested.set(onPlayRequested().get()); + } catch (ExecutionException | InterruptedException e) { + playRequested.setException(new IllegalStateException(e)); + } + }); + return playRequested; } if (this.mediaSessionListener != null) { - return this.mediaSessionListener.onPlayRequested(instance); + return Futures.immediateFuture(this.mediaSessionListener.onPlayRequested(instance)); } - return true; + return Futures.immediateFuture(true); } /** @@ -962,83 +965,102 @@ protected MediaSessionServiceLegacyStub getLegacyBrowserService() { * * @param controller The controller requesting to play. */ - /* package */ void handleMediaControllerPlayRequest( + /* package */ ListenableFuture<SessionResult> handleMediaControllerPlayRequest( ControllerInfo controller, boolean callOnPlayerInteractionFinished) { - if (!onPlayRequested()) { - // Request denied, e.g. due to missing foreground service abilities. - return; - } - boolean hasCurrentMediaItem = - playerWrapper.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) - && playerWrapper.getCurrentMediaItem() != null; - boolean canAddMediaItems = - playerWrapper.isCommandAvailable(COMMAND_SET_MEDIA_ITEM) - || playerWrapper.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS); - ControllerInfo controllerForRequest = resolveControllerInfoForCallback(controller); - Player.Commands playCommand = - new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build(); - if (hasCurrentMediaItem || !canAddMediaItems) { - // No playback resumption needed or possible. - if (!hasCurrentMediaItem) { - Log.w( - TAG, - "Play requested without current MediaItem, but playback resumption prevented by" - + " missing available commands"); - } - Util.handlePlayButtonAction(playerWrapper); - if (callOnPlayerInteractionFinished) { - onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand); - } - } else { - @Nullable - ListenableFuture<MediaItemsWithStartPosition> future = - checkNotNull( - callback.onPlaybackResumption(instance, controllerForRequest), - "Callback.onPlaybackResumption must return a non-null future"); - Futures.addCallback( - future, - new FutureCallback<MediaItemsWithStartPosition>() { - @Override - public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) { - callWithControllerForCurrentRequestSet( - controllerForRequest, - () -> { - MediaUtils.setMediaItemsWithStartIndexAndPosition( - playerWrapper, mediaItemsWithStartPosition); - Util.handlePlayButtonAction(playerWrapper); - if (callOnPlayerInteractionFinished) { - onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand); - } - }) - .run(); + SettableFuture<SessionResult> sessionFuture = SettableFuture.create(); + ListenableFuture<Boolean> playRequestedFuture = onPlayRequested(); + playRequestedFuture.addListener( + () -> { + boolean playRequested; + try { + playRequested = playRequestedFuture.get(); + } catch (ExecutionException | InterruptedException e) { + sessionFuture.setException(new IllegalStateException(e)); + return; + } + if (!playRequested) { + // Request denied, e.g. due to missing foreground service abilities. + sessionFuture.set(new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN)); + return; + } + boolean hasCurrentMediaItem = + playerWrapper.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) + && playerWrapper.getCurrentMediaItem() != null; + boolean canAddMediaItems = + playerWrapper.isCommandAvailable(COMMAND_SET_MEDIA_ITEM) + || playerWrapper.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS); + ControllerInfo controllerForRequest = resolveControllerInfoForCallback(controller); + Player.Commands playCommand = + new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build(); + if (hasCurrentMediaItem || !canAddMediaItems) { + // No playback resumption needed or possible. + if (!hasCurrentMediaItem) { + Log.w( + TAG, + "Play requested without current MediaItem, but playback resumption prevented by" + + " missing available commands"); } - - @Override - public void onFailure(Throwable t) { - if (t instanceof UnsupportedOperationException) { - Log.w( - TAG, - "UnsupportedOperationException: Make sure to implement" - + " MediaSession.Callback.onPlaybackResumption() if you add a" - + " media button receiver to your manifest or if you implement the recent" - + " media item contract with your MediaLibraryService.", - t); - } else { - Log.e( - TAG, - "Failure calling MediaSession.Callback.onPlaybackResumption(): " - + t.getMessage(), - t); - } - // Play as requested even if playback resumption fails. - Util.handlePlayButtonAction(playerWrapper); - if (callOnPlayerInteractionFinished) { - onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand); - } + Util.handlePlayButtonAction(playerWrapper); + sessionFuture.set(new SessionResult(SessionResult.RESULT_SUCCESS)); + if (callOnPlayerInteractionFinished) { + onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand); } - }, - this::postOrRunOnApplicationHandler); - } + } else { + @Nullable + ListenableFuture<MediaItemsWithStartPosition> future = + checkNotNull( + callback.onPlaybackResumption(instance, controllerForRequest), + "Callback.onPlaybackResumption must return a non-null future"); + Futures.addCallback( + future, + new FutureCallback<MediaItemsWithStartPosition>() { + @Override + public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) { + callWithControllerForCurrentRequestSet( + controllerForRequest, + () -> { + MediaUtils.setMediaItemsWithStartIndexAndPosition( + playerWrapper, mediaItemsWithStartPosition); + Util.handlePlayButtonAction(playerWrapper); + sessionFuture.set(new SessionResult(SessionResult.RESULT_SUCCESS)); + if (callOnPlayerInteractionFinished) { + onPlayerInteractionFinishedOnHandler( + controllerForRequest, playCommand); + } + }) + .run(); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof UnsupportedOperationException) { + Log.w( + TAG, + "UnsupportedOperationException: Make sure to implement" + + " MediaSession.Callback.onPlaybackResumption() if you add a media" + + " button receiver to your manifest or if you implement the recent" + + " media item contract with your MediaLibraryService.", + t); + } else { + Log.e( + TAG, + "Failure calling MediaSession.Callback.onPlaybackResumption(): " + + t.getMessage(), + t); + } + // Play as requested even if playback resumption fails. + Util.handlePlayButtonAction(playerWrapper); + sessionFuture.set(new SessionResult(SessionResult.RESULT_SUCCESS)); + if (callOnPlayerInteractionFinished) { + onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand); + } + } + }, + this::postOrRunOnApplicationHandler); + } + }, + this::postOrRunOnApplicationHandler); + return sessionFuture; } private void setLegacyMediaButtonPreferences( diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java index affbc7de1f3..31d40b0fd53 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -390,9 +390,21 @@ public void onPrepareFromUri(@Nullable Uri mediaUri, @Nullable Bundle extras) { public void onPlay() { dispatchSessionTaskWithPlayerCommand( COMMAND_PLAY_PAUSE, - controller -> - sessionImpl.handleMediaControllerPlayRequest( - controller, /* callOnPlayerInteractionFinished= */ true), + controller -> { + try { + // Call get() to make sure the call is blocking. + SessionResult result = + sessionImpl + .handleMediaControllerPlayRequest( + controller, /* callOnPlayerInteractionFinished= */ true) + .get(); + if (result.resultCode != RESULT_SUCCESS) { + Log.w(TAG, "onPlay() failed: " + result + " (from: " + controller + ")"); + } + } catch (ExecutionException | InterruptedException e) { + throw new IllegalStateException("Unexpected exception in onPlay() of " + controller, e); + } + }, sessionCompat.getCurrentControllerInfo(), /* callOnPlayerInteractionFinished= */ false); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java index 98461818968..213be425a3b 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java @@ -732,15 +732,10 @@ public void playForControllerInfo(ControllerInfo controller, int sequenceNumber) controller, sequenceNumber, COMMAND_PLAY_PAUSE, - sendSessionResultSuccess( - player -> { - @Nullable MediaSessionImpl impl = sessionImpl.get(); - if (impl == null || impl.isReleased()) { - return; - } - impl.handleMediaControllerPlayRequest( - controller, /* callOnPlayerInteractionFinished= */ false); - })); + sendSessionResultWhenReady( + (session, theController, sequenceId) -> + session.handleMediaControllerPlayRequest( + theController, /* callOnPlayerInteractionFinished= */ false))); } @Override