Skip to content

Commit 7bce838

Browse files
AquilesCantatonihei
authored andcommitted
Fix transfer from Cast to Local
This CL fixes a regression where, upon a transfer to "This phone", the queue callback would send an empty queue event before onSessionEnded. This means it's not possible to transfer the playback queue back to local, since it's empty from the point of view of the RemoteCastPlayer client. To fix this, we assume the session is ended earlier, in onSessionEnding. And we also prevent updating the timeline if the session is not active. This prevents us from updating the timeline upon a queue event after onSessionEnding. PiperOrigin-RevId: 862208017 (cherry picked from commit 481a2be)
1 parent 622f473 commit 7bce838

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,10 @@ private void updateRepeatModeAndNotifyIfChanged(@Nullable ResultCallback<?> resu
12791279
*/
12801280
@SuppressWarnings("deprecation") // Calling deprecated listener method.
12811281
private boolean updateTimelineAndNotifyIfChanged() {
1282+
if (remoteMediaClient == null) {
1283+
// There is no session. We leave the state of the player as it is now.
1284+
return false;
1285+
}
12821286
Timeline oldTimeline = currentTimeline;
12831287
int oldWindowIndex = currentWindowIndex;
12841288
boolean playingPeriodChanged = false;
@@ -1858,7 +1862,12 @@ public void onSessionStartFailed(CastSession castSession, int statusCode) {
18581862

18591863
@Override
18601864
public void onSessionEnding(CastSession castSession) {
1861-
// Do nothing.
1865+
// We don't wait until the onSessionEnded event because the media queue callback will send
1866+
// updates before that (but after onSessionEnding) indicating the queue is being cleared. This
1867+
// complicates playback state transfer to local. As a result, we clear the active cast session
1868+
// here so that the player keeps reflecting the state at this point in time, before the queue
1869+
// is cleared. See [internal: b/479112029].
1870+
setCastSession(null);
18621871
}
18631872

18641873
@Override

libraries/cast/src/test/java/androidx/media3/cast/RemoteCastPlayerTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import com.google.android.gms.cast.framework.CastContext;
9090
import com.google.android.gms.cast.framework.CastSession;
9191
import com.google.android.gms.cast.framework.SessionManager;
92+
import com.google.android.gms.cast.framework.SessionManagerListener;
9293
import com.google.android.gms.cast.framework.media.MediaQueue;
9394
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
9495
import com.google.android.gms.common.api.PendingResult;
@@ -115,6 +116,7 @@ public class RemoteCastPlayerTest {
115116
private RemoteCastPlayer remoteCastPlayer;
116117
private DefaultMediaItemConverter mediaItemConverter;
117118
private Cast.Listener castListener;
119+
private SessionManagerListener<CastSession> sessionManagerListener;
118120
private RemoteMediaClient.Callback remoteMediaClientCallback;
119121
private MediaQueue.Callback mediaQueueCallback;
120122

@@ -133,6 +135,8 @@ public class RemoteCastPlayerTest {
133135

134136
@Captor private ArgumentCaptor<Cast.Listener> castListenerArgumentCaptor;
135137

138+
@Captor private ArgumentCaptor<SessionManagerListener<CastSession>> sessionManagerListenerCaptor;
139+
136140
@Captor
137141
private ArgumentCaptor<RemoteMediaClient.Callback> remoteMediaClientCallbackArgumentCaptor;
138142

@@ -167,6 +171,9 @@ public void setUp() {
167171
C.DEFAULT_SEEK_FORWARD_INCREMENT_MS,
168172
C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS);
169173
remoteCastPlayer.addListener(mockListener);
174+
verify(mockSessionManager)
175+
.addSessionManagerListener(sessionManagerListenerCaptor.capture(), eq(CastSession.class));
176+
sessionManagerListener = sessionManagerListenerCaptor.getValue();
170177
verify(mockCastSession).addCastListener(castListenerArgumentCaptor.capture());
171178
castListener = castListenerArgumentCaptor.getValue();
172179
verify(mockRemoteMediaClient)
@@ -2382,6 +2389,41 @@ private void updateTimeLine(
23822389
}
23832390
}
23842391

2392+
@Test
2393+
public void mediaQueueChanged_afterOnSessionEnding_doesNotClearTheTimeline() {
2394+
List<MediaItem> firstPlaylist = new ArrayList<>();
2395+
String uri1 = "http://www.google.com/video1";
2396+
String uri2 = "http://www.google.com/video2";
2397+
int firstItemId = 33;
2398+
firstPlaylist.add(
2399+
new MediaItem.Builder().setUri(uri1).setMimeType(MimeTypes.APPLICATION_MPD).build());
2400+
firstPlaylist.add(
2401+
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
2402+
remoteCastPlayer.setMediaItems(
2403+
firstPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
2404+
when(mockRemoteMediaClient.getPlayerState()).thenReturn(MediaStatus.PLAYER_STATE_BUFFERING);
2405+
updateTimeLine(
2406+
firstPlaylist, /* mediaQueueItemIds= */ new int[] {firstItemId, 2}, /* currentItemId= */ 1);
2407+
mediaQueueCallback.mediaQueueChanged();
2408+
clearInvocations(mockListener);
2409+
Timeline initialTimeline = remoteCastPlayer.getCurrentTimeline();
2410+
2411+
sessionManagerListener.onSessionEnding(mockCastSession);
2412+
updateTimeLine(
2413+
/* mediaItems= */ ImmutableList.of(),
2414+
/* mediaQueueItemIds= */ new int[] {},
2415+
/* currentItemId= */ C.INDEX_UNSET,
2416+
/* streamTypes= */ new int[] {},
2417+
/* durationsMs= */ new long[] {},
2418+
/* positionMs= */ 0,
2419+
/* notifyStatusUpdate= */ true);
2420+
mediaQueueCallback.mediaQueueChanged();
2421+
2422+
Timeline timelineAfterSessionEnd = remoteCastPlayer.getCurrentTimeline();
2423+
verify(mockListener, never()).onTimelineChanged(any(), anyInt());
2424+
assertThat(initialTimeline).isEqualTo(timelineAfterSessionEnd);
2425+
}
2426+
23852427
private static Player.Commands createWithDefaultCommands(
23862428
boolean isTimelineEmpty, @Player.Command int... additionalCommands) {
23872429
Player.Commands.Builder builder = new Player.Commands.Builder();

0 commit comments

Comments
 (0)