Commit 41a2f9a6 by olly Committed by Ian Baker

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
parent 546d9f59
...@@ -148,10 +148,12 @@ public abstract class MediaLibraryService extends MediaSessionService { ...@@ -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 * MediaItem#mediaId}. The media id is required for the browser to get the children under the
* root. * root.
* *
* <p>Interoperability: This method may be called on the main thread regardless of the * <p>Interoperability: If this callback is called because a legacy {@link
* application thread if legacy {@link android.support.v4.media.MediaBrowserCompat} requested * android.support.v4.media.MediaBrowserCompat} has requested a {@link
* a {@link androidx.media.MediaBrowserServiceCompat.BrowserRoot}. In this case, you must * androidx.media.MediaBrowserServiceCompat.BrowserRoot}, then the main thread may be blocked
* return a completed future. * 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 session The session for this event.
* @param browser The browser information. * @param browser The browser information.
......
...@@ -35,6 +35,7 @@ import androidx.core.util.ObjectsCompat; ...@@ -35,6 +35,7 @@ import androidx.core.util.ObjectsCompat;
import androidx.media.MediaBrowserServiceCompat; import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager.RemoteUserInfo; import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.session.MediaLibraryService.LibraryParams;
...@@ -49,6 +50,7 @@ import java.util.List; ...@@ -49,6 +50,7 @@ import java.util.List;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link * Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
...@@ -89,22 +91,24 @@ import java.util.concurrent.Future; ...@@ -89,22 +91,24 @@ import java.util.concurrent.Future;
@Nullable @Nullable
LibraryParams params = LibraryParams params =
MediaUtils.convertToLibraryParams(librarySessionImpl.getContext(), rootHints); MediaUtils.convertToLibraryParams(librarySessionImpl.getContext(), rootHints);
// Call onGetLibraryRoot() directly instead of posting to the application thread not to block AtomicReference<ListenableFuture<LibraryResult<MediaItem>>> futureReference =
// the main thread as MediaBrowserServiceCompat requires to return browser root here. new AtomicReference<>();
// onGetLibraryRoot() has documentation that it may be called on the main thread. ConditionVariable haveFuture = new ConditionVariable();
ListenableFuture<LibraryResult<MediaItem>> future = postOrRun(
librarySessionImpl.getApplicationHandler(),
() -> {
futureReference.set(
checkNotNull( checkNotNull(
librarySessionImpl librarySessionImpl
.getCallback() .getCallback()
.onGetLibraryRoot(librarySessionImpl.getInstance(), controller, params), .onGetLibraryRoot(librarySessionImpl.getInstance(), controller, params),
"onGetLibraryRoot must return non-null future"); "onGetLibraryRoot must return non-null future"));
if (!future.isDone()) { haveFuture.open();
throw new RuntimeException( });
"onGetLibraryRoot must return a completed future " + "for legacy MediaBrowserCompat");
}
@Nullable LibraryResult<MediaItem> result = null; @Nullable LibraryResult<MediaItem> result = null;
try { 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) { } catch (CancellationException | ExecutionException | InterruptedException e) {
Log.e(TAG, "Couldn't get a result from onGetLibraryRoot", e); Log.e(TAG, "Couldn't get a result from onGetLibraryRoot", e);
} }
......
...@@ -792,8 +792,10 @@ public class MediaSession { ...@@ -792,8 +792,10 @@ public class MediaSession {
* #sendCustomCommand}, {@link #setCustomLayout}) will be ignored. Use {@link #onPostConnect} * #sendCustomCommand}, {@link #setCustomLayout}) will be ignored. Use {@link #onPostConnect}
* for custom initialization of the controller instead. * for custom initialization of the controller instead.
* *
* <p>Interoperability: this callback may be called on the main thread, regardless of the * <p>Interoperability: If a legacy controller is connecting to the session then this callback
* application thread. * 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 session The session for this event.
* @param controller The controller information. * @param controller The controller information.
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package androidx.media3.session; 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.os.Bundle;
import android.support.v4.media.MediaBrowserCompat.MediaItem; import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
...@@ -22,8 +25,11 @@ import androidx.annotation.Nullable; ...@@ -22,8 +25,11 @@ import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat; import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager; import androidx.media.MediaSessionManager;
import androidx.media.MediaSessionManager.RemoteUserInfo; import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.session.MediaSession.ControllerInfo; import androidx.media3.session.MediaSession.ControllerInfo;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link * Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
...@@ -31,6 +37,8 @@ import java.util.List; ...@@ -31,6 +37,8 @@ import java.util.List;
*/ */
/* package */ class MediaSessionServiceLegacyStub extends MediaBrowserServiceCompat { /* package */ class MediaSessionServiceLegacyStub extends MediaBrowserServiceCompat {
private static final String TAG = "MSSLegacyStub";
private final MediaSessionManager manager; private final MediaSessionManager manager;
private final MediaSession.MediaSessionImpl sessionImpl; private final MediaSession.MediaSessionImpl sessionImpl;
private final ConnectedControllersManager<RemoteUserInfo> connectedControllersManager; private final ConnectedControllersManager<RemoteUserInfo> connectedControllersManager;
...@@ -55,19 +63,30 @@ import java.util.List; ...@@ -55,19 +63,30 @@ import java.util.List;
String clientPackageName, int clientUid, @Nullable Bundle rootHints) { String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
RemoteUserInfo info = getCurrentBrowserInfo(); RemoteUserInfo info = getCurrentBrowserInfo();
MediaSession.ControllerInfo controller = createControllerInfo(info); 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. AtomicReference<MediaSession.ConnectionResult> resultReference = new AtomicReference<>();
// onConnect() has documentation that it may be called on the main thread. ConditionVariable haveResult = new ConditionVariable();
MediaSession.ConnectionResult connectionResult = postOrRun(
sessionImpl.getCallback().onConnect(sessionImpl.getInstance(), controller); sessionImpl.getApplicationHandler(),
if (!connectionResult.isAccepted) { () -> {
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; return null;
} }
connectedControllersManager.addController( connectedControllersManager.addController(
info, info, controller, result.availableSessionCommands, result.availablePlayerCommands);
controller,
connectionResult.availableSessionCommands,
connectionResult.availablePlayerCommands);
// No library root, but keep browser compat connected to allow getting session. // No library root, but keep browser compat connected to allow getting session.
return MediaUtils.defaultBrowserRoot; return MediaUtils.defaultBrowserRoot;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment