Skip to content

Commit a2cdbfc

Browse files
Googlercopybara-github
authored andcommitted
Use CastContextWrapper in MediaRouteButtonFactory
Migrate the MediaRouteButtonFactory to use the CastContextWrapper class. Due to the async initialization of the CastContext, the MediaRouteButtonFactory registers listeners to the CastContextWrapper for monitoring the initialization of the Cast Context. PiperOrigin-RevId: 846794943
1 parent 1c58fa0 commit a2cdbfc

File tree

10 files changed

+686
-51
lines changed

10 files changed

+686
-51
lines changed

libraries/cast/src/main/java/androidx/media3/cast/CastContextWrapper.java

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,28 @@
1515
*/
1616
package androidx.media3.cast;
1717

18+
import static androidx.media3.cast.CastUtils.verifyMainThread;
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
1821
import android.annotation.SuppressLint;
1922
import android.content.Context;
20-
import android.os.Looper;
2123
import androidx.annotation.MainThread;
2224
import androidx.annotation.Nullable;
2325
import androidx.annotation.VisibleForTesting;
24-
import androidx.core.util.Preconditions;
2526
import androidx.media3.common.util.BackgroundExecutor;
2627
import androidx.media3.common.util.Log;
2728
import androidx.media3.common.util.UnstableApi;
29+
import androidx.mediarouter.media.MediaRouteSelector;
2830
import com.google.android.gms.cast.framework.CastContext;
2931
import com.google.android.gms.cast.framework.CastSession;
3032
import com.google.android.gms.cast.framework.SessionManager;
3133
import com.google.android.gms.cast.framework.SessionManagerListener;
3234
import com.google.android.gms.tasks.Task;
3335
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3436
import java.util.ArrayList;
37+
import java.util.HashSet;
3538
import java.util.List;
39+
import java.util.Set;
3640
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
3741

3842
/**
@@ -54,14 +58,17 @@ public class CastContextWrapper {
5458
@Nullable
5559
private static CastContextWrapper singletonInstance;
5660

57-
@Nullable private CastContext castContext;
5861
private final List<SessionManagerListener<CastSession>> pendingListeners;
62+
63+
private final Set<MediaRouteSelectorListener> pendingMediaRouteSelectorListeners;
64+
65+
@Nullable private CastContext castContext;
5966
private @MonotonicNonNull Throwable castContextLoadFailure;
6067
private boolean isInitOngoing;
6168

6269
/** Returns a singleton instance of {@link CastContextWrapper}. */
6370
public static CastContextWrapper getSingletonInstance() {
64-
checkRunningOnMainThread();
71+
verifyMainThread();
6572
if (singletonInstance == null) {
6673
singletonInstance = new CastContextWrapper();
6774
}
@@ -76,8 +83,8 @@ public static CastContextWrapper getSingletonInstance() {
7683
*/
7784
@CanIgnoreReturnValue
7885
public CastContextWrapper initWithContext(CastContext castContext) {
79-
checkRunningOnMainThread();
80-
Preconditions.checkNotNull(castContext);
86+
verifyMainThread();
87+
checkNotNull(castContext);
8188
if (needsInitialization()) {
8289
setCastContext(castContext);
8390
}
@@ -99,7 +106,7 @@ public void asyncInit(Context context) {
99106

100107
@VisibleForTesting
101108
/* package */ void asyncInit(CastContextInitializer castContextInitializer) {
102-
checkRunningOnMainThread();
109+
verifyMainThread();
103110
if (!needsInitialization()) {
104111
Log.w(TAG, "Tried to initialize an already initialized CastContextWrapper.");
105112
return;
@@ -147,7 +154,7 @@ public Throwable getCastContextLoadFailure() {
147154
* @param listener The listener to register.
148155
*/
149156
public void addSessionManagerListener(SessionManagerListener<CastSession> listener) {
150-
checkRunningOnMainThread();
157+
verifyMainThread();
151158
if (castContext != null) {
152159
castContext.getSessionManager().addSessionManagerListener(listener, CastSession.class);
153160
} else {
@@ -157,21 +164,40 @@ public void addSessionManagerListener(SessionManagerListener<CastSession> listen
157164

158165
/** Unregisters the given session manager listener. */
159166
public void removeSessionManagerListener(SessionManagerListener<CastSession> listener) {
160-
checkRunningOnMainThread();
167+
verifyMainThread();
161168
if (castContext != null) {
162169
castContext.getSessionManager().removeSessionManagerListener(listener, CastSession.class);
163170
} else {
164171
pendingListeners.remove(listener);
165172
}
166173
}
167174

175+
@Nullable
176+
/* package */ MediaRouteSelector registerListenerAndGetCurrentSelector(
177+
MediaRouteSelectorListener listener) {
178+
checkNotNull(listener);
179+
if (castContext != null) {
180+
MediaRouteSelector selector = castContext.getMergedSelector();
181+
return (selector == null) ? MediaRouteSelector.EMPTY : selector;
182+
}
183+
if (castContextLoadFailure != null) {
184+
return MediaRouteSelector.EMPTY;
185+
}
186+
pendingMediaRouteSelectorListeners.add(listener);
187+
return null;
188+
}
189+
190+
/* package */ void unregisterListener(MediaRouteSelectorListener listener) {
191+
pendingMediaRouteSelectorListeners.remove(listener);
192+
}
193+
168194
/**
169195
* Returns the ongoing Cast session, or null if there's no ongoing Cast session, or there's no
170196
* Cast context available.
171197
*/
172198
@Nullable
173199
public CastSession getCurrentCastSession() {
174-
checkRunningOnMainThread();
200+
verifyMainThread();
175201
return castContext != null ? castContext.getSessionManager().getCurrentCastSession() : null;
176202
}
177203

@@ -181,14 +207,14 @@ public CastSession getCurrentCastSession() {
181207
* @param stopCasting Whether to stop the receiver application.
182208
*/
183209
public void endCurrentSession(boolean stopCasting) {
184-
checkRunningOnMainThread();
210+
verifyMainThread();
185211
if (castContext != null) {
186212
castContext.getSessionManager().endCurrentSession(stopCasting);
187213
}
188214
}
189215

190216
private void setCastContext(CastContext castContext) {
191-
checkRunningOnMainThread();
217+
verifyMainThread();
192218
isInitOngoing = false;
193219
this.castContext = castContext;
194220
SessionManager sessionManager = castContext.getSessionManager();
@@ -205,6 +231,9 @@ private void setCastContext(CastContext castContext) {
205231
}
206232
}
207233
pendingListeners.clear();
234+
MediaRouteSelector selector = castContext.getMergedSelector();
235+
selector = (selector == null) ? MediaRouteSelector.EMPTY : selector;
236+
notifyPendingMediaRouteSelectorListeners(selector);
208237
}
209238

210239
private void onCastContextLoadFailure(@Nullable Exception exception) {
@@ -214,25 +243,36 @@ private void onCastContextLoadFailure(@Nullable Exception exception) {
214243
? exception
215244
: new UnknownError("Cast context load failed with a null exception.");
216245
Log.e(TAG, "Failed to load CastContext", castContextLoadFailure);
246+
// Cast context load has failed, the selector won't become non-empty. Notifying listeners with
247+
// an empty selector ensures that listeners are not left pending forever.
248+
notifyPendingMediaRouteSelectorListeners(MediaRouteSelector.EMPTY);
249+
}
250+
251+
private void notifyPendingMediaRouteSelectorListeners(MediaRouteSelector selector) {
252+
for (MediaRouteSelectorListener listener : pendingMediaRouteSelectorListeners) {
253+
listener.onMediaRouteSelectorChanged(selector);
254+
}
255+
pendingMediaRouteSelectorListeners.clear();
217256
}
218257

219258
private CastContextWrapper() {
220259
pendingListeners = new ArrayList<>();
260+
pendingMediaRouteSelectorListeners = new HashSet<>();
221261
}
222262

223263
@VisibleForTesting
224264
/* package */ static void reset() {
225-
checkRunningOnMainThread();
265+
verifyMainThread();
226266
singletonInstance = null;
227267
}
228268

229-
private static void checkRunningOnMainThread() {
230-
Preconditions.checkState(Looper.myLooper() == Looper.getMainLooper());
231-
}
232-
233269
@VisibleForTesting
234270
/* package */ interface CastContextInitializer {
235271

236272
Task<CastContext> init();
237273
}
274+
275+
/* package */ abstract static class MediaRouteSelectorListener {
276+
void onMediaRouteSelectorChanged(MediaRouteSelector selector) {}
277+
}
238278
}

libraries/cast/src/main/java/androidx/media3/cast/CastUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package androidx.media3.cast;
1717

18+
import android.os.Looper;
1819
import androidx.annotation.Nullable;
20+
import androidx.core.util.Preconditions;
1921
import androidx.media3.common.C;
2022
import androidx.media3.common.C.TrackType;
2123
import androidx.media3.common.Format;
@@ -29,6 +31,15 @@
2931
/** Utility methods for Cast integration. */
3032
/* package */ final class CastUtils {
3133

34+
/**
35+
* Verifies that the current thread is the main thread.
36+
*
37+
* @throws IllegalStateException if the current thread is not the main thread.
38+
*/
39+
public static void verifyMainThread() {
40+
Preconditions.checkState(Looper.myLooper() == Looper.getMainLooper());
41+
}
42+
3243
/**
3344
* Returns the duration in microseconds advertised by a media info, or {@link C#TIME_UNSET} if
3445
* unknown or not applicable.

0 commit comments

Comments
 (0)