Simplify threading for media3 session callbacks

This change removes the requirement that callback implementations
need to be able to handle two specific callbacks being called on
two different threads.

PiperOrigin-RevId: 413958545
This commit is contained in:
olly 2021-12-03 17:52:35 +00:00 committed by Ian Baker
parent 546d9f592b
commit 41a2f9a6b3
4 changed files with 57 additions and 30 deletions

View File

@ -148,10 +148,12 @@ public abstract class MediaLibraryService extends MediaSessionService {
* MediaItem#mediaId}. The media id is required for the browser to get the children under the
* root.
*
* <p>Interoperability: This method may be called on the main thread regardless of the
* application thread if legacy {@link android.support.v4.media.MediaBrowserCompat} requested
* a {@link androidx.media.MediaBrowserServiceCompat.BrowserRoot}. In this case, you must
* return a completed future.
* <p>Interoperability: If this callback is called because a legacy {@link
* android.support.v4.media.MediaBrowserCompat} has requested a {@link
* androidx.media.MediaBrowserServiceCompat.BrowserRoot}, then the main thread may be blocked
* until the returned future is done. If your service may be queried by a legacy {@link
* android.support.v4.media.MediaBrowserCompat}, you should ensure that the future completes
* quickly to avoid blocking the main thread for a long period of time.
*
* @param session The session for this event.
* @param browser The browser information.

View File

@ -35,6 +35,7 @@ import androidx.core.util.ObjectsCompat;
import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams;
@ -49,6 +50,7 @@ import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
/**
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
@ -89,22 +91,24 @@ import java.util.concurrent.Future;
@Nullable
LibraryParams params =
MediaUtils.convertToLibraryParams(librarySessionImpl.getContext(), rootHints);
// Call onGetLibraryRoot() directly instead of posting to the application thread not to block
// the main thread as MediaBrowserServiceCompat requires to return browser root here.
// onGetLibraryRoot() has documentation that it may be called on the main thread.
ListenableFuture<LibraryResult<MediaItem>> future =
AtomicReference<ListenableFuture<LibraryResult<MediaItem>>> futureReference =
new AtomicReference<>();
ConditionVariable haveFuture = new ConditionVariable();
postOrRun(
librarySessionImpl.getApplicationHandler(),
() -> {
futureReference.set(
checkNotNull(
librarySessionImpl
.getCallback()
.onGetLibraryRoot(librarySessionImpl.getInstance(), controller, params),
"onGetLibraryRoot must return non-null future");
if (!future.isDone()) {
throw new RuntimeException(
"onGetLibraryRoot must return a completed future " + "for legacy MediaBrowserCompat");
}
"onGetLibraryRoot must return non-null future"));
haveFuture.open();
});
@Nullable LibraryResult<MediaItem> result = null;
try {
result = checkNotNull(future.get(), "LibraryResult must not be null");
haveFuture.block();
result = checkNotNull(futureReference.get().get(), "LibraryResult must not be null");
} catch (CancellationException | ExecutionException | InterruptedException e) {
Log.e(TAG, "Couldn't get a result from onGetLibraryRoot", e);
}

View File

@ -792,8 +792,10 @@ public class MediaSession {
* #sendCustomCommand}, {@link #setCustomLayout}) will be ignored. Use {@link #onPostConnect}
* for custom initialization of the controller instead.
*
* <p>Interoperability: this callback may be called on the main thread, regardless of the
* application thread.
* <p>Interoperability: If a legacy controller is connecting to the session then this callback
* may block the main thread, even if it's called on a different application thread. If it's
* possible that legacy controllers will connect to the session, you should ensure that the
* callback returns quickly to avoid blocking the main thread for a long period of time.
*
* @param session The session for this event.
* @param controller The controller information.

View File

@ -15,6 +15,9 @@
*/
package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.postOrRun;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.MediaSessionCompat;
@ -22,8 +25,11 @@ import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager;
import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.session.MediaSession.ControllerInfo;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
@ -31,6 +37,8 @@ import java.util.List;
*/
/* package */ class MediaSessionServiceLegacyStub extends MediaBrowserServiceCompat {
private static final String TAG = "MSSLegacyStub";
private final MediaSessionManager manager;
private final MediaSession.MediaSessionImpl sessionImpl;
private final ConnectedControllersManager<RemoteUserInfo> connectedControllersManager;
@ -55,19 +63,30 @@ import java.util.List;
String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
RemoteUserInfo info = getCurrentBrowserInfo();
MediaSession.ControllerInfo controller = createControllerInfo(info);
// Call onConnect() directly instead of posting to the application thread not to block the main
// thread as MediaBrowserServiceCompat requires to return browser root here.
// onConnect() has documentation that it may be called on the main thread.
MediaSession.ConnectionResult connectionResult =
sessionImpl.getCallback().onConnect(sessionImpl.getInstance(), controller);
if (!connectionResult.isAccepted) {
AtomicReference<MediaSession.ConnectionResult> resultReference = new AtomicReference<>();
ConditionVariable haveResult = new ConditionVariable();
postOrRun(
sessionImpl.getApplicationHandler(),
() -> {
resultReference.set(
checkNotNull(
sessionImpl.getCallback().onConnect(sessionImpl.getInstance(), controller),
"onConnect must return non-null future"));
haveResult.open();
});
try {
haveResult.block();
} catch (InterruptedException e) {
Log.e(TAG, "Couldn't get a result from onConnect", e);
return null;
}
MediaSession.ConnectionResult result = resultReference.get();
if (!result.isAccepted) {
return null;
}
connectedControllersManager.addController(
info,
controller,
connectionResult.availableSessionCommands,
connectionResult.availablePlayerCommands);
info, controller, result.availableSessionCommands, result.availablePlayerCommands);
// No library root, but keep browser compat connected to allow getting session.
return MediaUtils.defaultBrowserRoot;
}