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:
parent
546d9f592b
commit
41a2f9a6b3
@ -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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
@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(
|
||||||
checkNotNull(
|
librarySessionImpl.getApplicationHandler(),
|
||||||
librarySessionImpl
|
() -> {
|
||||||
.getCallback()
|
futureReference.set(
|
||||||
.onGetLibraryRoot(librarySessionImpl.getInstance(), controller, params),
|
checkNotNull(
|
||||||
"onGetLibraryRoot must return non-null future");
|
librarySessionImpl
|
||||||
if (!future.isDone()) {
|
.getCallback()
|
||||||
throw new RuntimeException(
|
.onGetLibraryRoot(librarySessionImpl.getInstance(), controller, params),
|
||||||
"onGetLibraryRoot must return a completed future " + "for legacy MediaBrowserCompat");
|
"onGetLibraryRoot must return non-null future"));
|
||||||
}
|
haveFuture.open();
|
||||||
|
});
|
||||||
@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 {
|
|||||||
* #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 @@
|
|||||||
*/
|
*/
|
||||||
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;
|
|||||||
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;
|
|||||||
*/
|
*/
|
||||||
/* 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;
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user