1515 */
1616package androidx .media3 .cast ;
1717
18+ import static androidx .media3 .common .util .Assertions .checkNotNull ;
19+
1820import android .content .Context ;
1921import androidx .annotation .IntRange ;
2022import androidx .annotation .Nullable ;
2325import androidx .media3 .common .ForwardingPlayer ;
2426import androidx .media3 .common .MediaItem ;
2527import androidx .media3 .common .Player ;
28+ import androidx .media3 .common .PlayerTransferState ;
29+ import androidx .media3 .common .util .Assertions ;
2630import androidx .media3 .common .util .UnstableApi ;
31+ import androidx .media3 .exoplayer .ExoPlayer ;
2732import com .google .android .gms .cast .framework .CastContext ;
33+ import com .google .errorprone .annotations .CanIgnoreReturnValue ;
34+ import org .checkerframework .checker .nullness .qual .MonotonicNonNull ;
2835
2936/**
30- * {@link Player} implementation that communicates with a Cast receiver app.
37+ * {@link Player} implementation that can control playback both on the local device, and on a remote
38+ * Cast device.
39+ *
40+ * <p>See {@link RemoteCastPlayer} for a {@link Player} that only supports playback on Cast
41+ * receivers.
42+ *
43+ * <p>This class works by delegating playback to a dedicated player depending on Cast session
44+ * availability. When a Cast session becomes available or unavailable, the following steps take
45+ * place:
3146 *
32- * <p>This class is a passthrough wrapper of {@link RemoteCastPlayer}.
47+ * <ul>
48+ * <li>The new active player is a {@link RemoteCastPlayer} if a Cast session is active, or the
49+ * {@link Builder#setLocalPlayer local player} otherwise.
50+ * <li>A customizable {@link TransferCallback} receives both players to transfer state across
51+ * players.
52+ * <li>The inactive player is {@link Player#stop() stopped}.
53+ * </ul>
3354 */
34- // TODO: b/419816002 - Deprecate constructors in this class and update javadocs once
35- // RemoteCastPlayer has a Builder, and this class is able to support local playback.
3655@ UnstableApi
3756public final class CastPlayer extends ForwardingPlayer {
3857
58+ /**
59+ * Callback for moving state across players when transferring playback upon Cast session
60+ * availability changes.
61+ */
62+ public interface TransferCallback {
63+
64+ TransferCallback DEFAULT =
65+ (sourcePlayer , targetPlayer ) ->
66+ PlayerTransferState .fromPlayer (sourcePlayer ).setToPlayer (targetPlayer );
67+
68+ /**
69+ * Called immediately before changing the active {@link Player}, with the intended use of
70+ * transferring playback state.
71+ *
72+ * @param sourcePlayer The {@link Player} from which playback is transferring, from which to
73+ * fetch the state.
74+ * @param targetPlayer The {@link Player} to which playback is transferring, to populate with
75+ * state.
76+ * @see PlayerTransferState
77+ */
78+ void transferState (Player sourcePlayer , Player targetPlayer );
79+ }
80+
81+ /** Builder for {@link CastPlayer}. */
82+ public static final class Builder {
83+
84+ private final Context context ;
85+ private TransferCallback transferCallback ;
86+ private @ MonotonicNonNull Player localPlayer ;
87+ private @ MonotonicNonNull RemoteCastPlayer remotePlayer ;
88+ private boolean buildCalled ;
89+
90+ /**
91+ * Creates a builder.
92+ *
93+ * <p>The builder uses the following default values:
94+ *
95+ * <ul>
96+ * <li>{@link TransferCallback}: {@link TransferCallback#DEFAULT}.
97+ * <li>{@link RemoteCastPlayer}: {@link RemoteCastPlayer new
98+ * RemoteCastPlayer.Builder(context).build()}.
99+ * <li>{@link #setLocalPlayer}: {@link ExoPlayer new ExoPlayer.Builder(context).build()}.
100+ * </ul>
101+ *
102+ * @param context A {@link Context}.
103+ */
104+ public Builder (Context context ) {
105+ this .context = checkNotNull (context );
106+ transferCallback = TransferCallback .DEFAULT ;
107+ }
108+
109+ /**
110+ * Sets the {@link TransferCallback} to call when the active player changes.
111+ *
112+ * @param transferCallback A {@link TransferCallback}.
113+ * @return This builder.
114+ * @throws IllegalStateException If {@link #build()} has already been called on this builder
115+ * instance.
116+ */
117+ @ CanIgnoreReturnValue
118+ public Builder setTransferCallback (TransferCallback transferCallback ) {
119+ Assertions .checkState (!buildCalled );
120+ this .transferCallback = checkNotNull (transferCallback );
121+ return this ;
122+ }
123+
124+ /**
125+ * Sets the {@link Player} to use for local playback.
126+ *
127+ * @param localPlayer A {@link Player}.
128+ * @return This builder.
129+ * @throws IllegalStateException If {@link #build()} has already been called on this builder
130+ * instance.
131+ */
132+ @ CanIgnoreReturnValue
133+ public Builder setLocalPlayer (Player localPlayer ) {
134+ Assertions .checkState (!buildCalled );
135+ this .localPlayer = checkNotNull (localPlayer );
136+ return this ;
137+ }
138+
139+ /**
140+ * Sets the {@link RemoteCastPlayer} to use for remote playback.
141+ *
142+ * @param remotePlayer A {@link RemoteCastPlayer}.
143+ * @return This builder.
144+ * @throws IllegalStateException If {@link #build()} has already been called on this builder
145+ * instance.
146+ */
147+ @ CanIgnoreReturnValue
148+ public Builder setRemotePlayer (RemoteCastPlayer remotePlayer ) {
149+ Assertions .checkState (!buildCalled );
150+ this .remotePlayer = checkNotNull (remotePlayer );
151+ return this ;
152+ }
153+
154+ /**
155+ * Creates and returns the new {@link CastPlayerImpl} instance.
156+ *
157+ * @throws IllegalStateException If this method has already been called on this instance.
158+ */
159+ public CastPlayer build () {
160+ Assertions .checkState (!buildCalled );
161+ buildCalled = true ;
162+ if (localPlayer == null ) {
163+ localPlayer = new ExoPlayer .Builder (context ).build ();
164+ }
165+ if (remotePlayer == null ) {
166+ remotePlayer = new RemoteCastPlayer .Builder (context ).build ();
167+ }
168+ Player initialActivePlayer =
169+ remotePlayer .isCastSessionAvailable () ? remotePlayer : localPlayer ;
170+ CastPlayerImpl castPlayerImpl =
171+ new CastPlayerImpl (localPlayer , remotePlayer , initialActivePlayer , transferCallback );
172+ return new CastPlayer (castPlayerImpl , remotePlayer );
173+ }
174+ }
175+
39176 /** Same as {@link RemoteCastPlayer#DEVICE_INFO_REMOTE_EMPTY}. */
40177 public static final DeviceInfo DEVICE_INFO_REMOTE_EMPTY =
41178 RemoteCastPlayer .DEVICE_INFO_REMOTE_EMPTY ;
@@ -65,7 +202,11 @@ public CastPlayer(CastContext castContext) {
65202 *
66203 * @param castContext The context from which the cast session is obtained.
67204 * @param mediaItemConverter The {@link MediaItemConverter} to use.
205+ * @deprecated Use {@link RemoteCastPlayer.Builder} to create a {@link Player} for playback
206+ * exclusively on Cast receivers, or {@link Builder} for a {@link Player} that works both on
207+ * Cast receivers and locally.
68208 */
209+ @ Deprecated
69210 public CastPlayer (CastContext castContext , MediaItemConverter mediaItemConverter ) {
70211 this (
71212 castContext ,
@@ -83,7 +224,11 @@ public CastPlayer(CastContext castContext, MediaItemConverter mediaItemConverter
83224 * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds.
84225 * @throws IllegalArgumentException If {@code seekBackIncrementMs} or {@code
85226 * seekForwardIncrementMs} is non-positive.
227+ * @deprecated Use {@link RemoteCastPlayer.Builder} to create a {@link Player} for playback
228+ * exclusively on Cast receivers, or {@link Builder} for a {@link Player} that works both on
229+ * Cast receivers and locally.
86230 */
231+ @ Deprecated
87232 public CastPlayer (
88233 CastContext castContext ,
89234 MediaItemConverter mediaItemConverter ,
@@ -112,7 +257,11 @@ public CastPlayer(
112257 * @throws IllegalArgumentException If {@code seekBackIncrementMs} or {@code
113258 * seekForwardIncrementMs} is non-positive, or if {@code maxSeekToPreviousPositionMs} is
114259 * negative.
260+ * @deprecated Use {@link RemoteCastPlayer.Builder} to create a {@link Player} for playback
261+ * exclusively on Cast receivers, or {@link Builder} for a {@link Player} that works both on
262+ * Cast receivers and locally.
115263 */
264+ @ Deprecated
116265 public CastPlayer (
117266 @ Nullable Context context ,
118267 CastContext castContext ,
@@ -135,7 +284,18 @@ private CastPlayer(RemoteCastPlayer remoteCastPlayer) {
135284 this .remoteCastPlayer = remoteCastPlayer ;
136285 }
137286
138- /** Returns whether a cast session is available. */
287+ private CastPlayer (CastPlayerImpl castPlayerImpl , RemoteCastPlayer remoteCastPlayer ) {
288+ super (castPlayerImpl );
289+ this .remoteCastPlayer = remoteCastPlayer ;
290+ }
291+
292+ /**
293+ * Returns whether a cast session is available.
294+ *
295+ * @deprecated Use {@link #getDeviceInfo()} instead, and check for {@link
296+ * DeviceInfo#PLAYBACK_TYPE_REMOTE}.
297+ */
298+ @ Deprecated
139299 public boolean isCastSessionAvailable () {
140300 return remoteCastPlayer .isCastSessionAvailable ();
141301 }
@@ -144,7 +304,10 @@ public boolean isCastSessionAvailable() {
144304 * Sets a listener for updates on the cast session availability.
145305 *
146306 * @param listener The {@link SessionAvailabilityListener}, or null to clear the listener.
307+ * @deprecated Use {@link androidx.media3.common.Player.Listener#onDeviceInfoChanged} instead, and
308+ * check for {@link DeviceInfo#PLAYBACK_TYPE_REMOTE}.
147309 */
310+ @ Deprecated
148311 public void setSessionAvailabilityListener (@ Nullable SessionAvailabilityListener listener ) {
149312 remoteCastPlayer .setSessionAvailabilityListener (listener );
150313 }
0 commit comments