Commit dee83cc7 by bachinger Committed by Ian Baker

Update session position info on timeline change

This fixes an inconsistent state of the `PlayerInfo` when the index of the playing
media item is changed by a playlist modification. In this inconsistent state,
calling `Playerinfo.getCurrentMediaItem` can produce an
`ArrayIndexOutOfBoundException` (see stack trace in GH issue).

This change takes the following measurements:

- always update sessionPosition and timeline of the PlayerInfo together in
  `MediaSessionImpl.PlayerListener` where the PlayerInfo originates from
- add an assertion to avoid building a `PlayerInfo` instance in an inconsistent
  state
- reduce the window of opportunity for concurrent access to
  `mediaSessionImpl.playerInfo` when dispatching player info changes in
  `MediaSessionImpl`

Issue: androidx/media#51
PiperOrigin-RevId: 444812661
parent 9369348d
...@@ -80,6 +80,9 @@ ...@@ -80,6 +80,9 @@
* Session: * Session:
* Fix NPE in MediaControllerImplLegacy * Fix NPE in MediaControllerImplLegacy
([#59](https://github.com/androidx/media/pull/59)) ([#59](https://github.com/androidx/media/pull/59))
* Update session position info on timeline
change([#51](https://github.com/androidx/media/issues/51))
* Data sources: * Data sources:
* Rename `DummyDataSource` to `PlaceHolderDataSource`. * Rename `DummyDataSource` to `PlaceHolderDataSource`.
* Remove deprecated symbols: * Remove deprecated symbols:
......
...@@ -753,7 +753,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -753,7 +753,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int newCurrentMediaItemIndex = int newCurrentMediaItemIndex =
calculateCurrentItemIndexAfterAddItems(currentMediaItemIndex, index, mediaItems.size()); calculateCurrentItemIndexAfterAddItems(currentMediaItemIndex, index, mediaItems.size());
PlayerInfo maskedPlayerInfo = PlayerInfo maskedPlayerInfo =
controllerInfo.playerInfo.copyWithTimeline(newQueueTimeline, newCurrentMediaItemIndex); controllerInfo.playerInfo.copyWithTimelineAndMediaItemIndex(
newQueueTimeline, newCurrentMediaItemIndex);
ControllerInfo maskedControllerInfo = ControllerInfo maskedControllerInfo =
new ControllerInfo( new ControllerInfo(
maskedPlayerInfo, maskedPlayerInfo,
...@@ -801,7 +802,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -801,7 +802,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
+ " new current item"); + " new current item");
} }
PlayerInfo maskedPlayerInfo = PlayerInfo maskedPlayerInfo =
controllerInfo.playerInfo.copyWithTimeline(newQueueTimeline, newCurrentMediaItemIndex); controllerInfo.playerInfo.copyWithTimelineAndMediaItemIndex(
newQueueTimeline, newCurrentMediaItemIndex);
ControllerInfo maskedControllerInfo = ControllerInfo maskedControllerInfo =
new ControllerInfo( new ControllerInfo(
...@@ -861,7 +863,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -861,7 +863,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
QueueTimeline newQueueTimeline = QueueTimeline newQueueTimeline =
queueTimeline.copyWithMovedMediaItems(fromIndex, toIndex, newIndex); queueTimeline.copyWithMovedMediaItems(fromIndex, toIndex, newIndex);
PlayerInfo maskedPlayerInfo = PlayerInfo maskedPlayerInfo =
controllerInfo.playerInfo.copyWithTimeline(newQueueTimeline, newCurrentMediaItemIndex); controllerInfo.playerInfo.copyWithTimelineAndMediaItemIndex(
newQueueTimeline, newCurrentMediaItemIndex);
ControllerInfo maskedControllerInfo = ControllerInfo maskedControllerInfo =
new ControllerInfo( new ControllerInfo(
......
...@@ -366,7 +366,7 @@ import org.checkerframework.checker.initialization.qual.Initialized; ...@@ -366,7 +366,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
(controller, seq) -> controller.sendCustomCommand(seq, command, args)); (controller, seq) -> controller.sendCustomCommand(seq, command, args));
} }
private void dispatchOnPlayerInfoChanged(boolean excludeTimeline) { private void dispatchOnPlayerInfoChanged(PlayerInfo playerInfo, boolean excludeTimeline) {
List<ControllerInfo> controllers = List<ControllerInfo> controllers =
sessionStub.getConnectedControllersManager().getConnectedControllers(); sessionStub.getConnectedControllersManager().getConnectedControllers();
...@@ -910,7 +910,9 @@ import org.checkerframework.checker.initialization.qual.Initialized; ...@@ -910,7 +910,9 @@ import org.checkerframework.checker.initialization.qual.Initialized;
if (player == null) { if (player == null) {
return; return;
} }
session.playerInfo = session.playerInfo.copyWithTimeline(timeline); session.playerInfo =
session.playerInfo.copyWithTimelineAndSessionPositionInfo(
timeline, player.createSessionPositionInfoForBundling());
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onTimelineChanged(seq, timeline, reason)); (callback, seq) -> callback.onTimelineChanged(seq, timeline, reason));
...@@ -1177,9 +1179,10 @@ import org.checkerframework.checker.initialization.qual.Initialized; ...@@ -1177,9 +1179,10 @@ import org.checkerframework.checker.initialization.qual.Initialized;
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (msg.what == MSG_PLAYER_INFO_CHANGED) { if (msg.what == MSG_PLAYER_INFO_CHANGED) {
playerInfo = playerInfo =
playerInfo.copyWithSessionPositionInfo( playerInfo.copyWithTimelineAndSessionPositionInfo(
getPlayerWrapper().getCurrentTimeline(),
getPlayerWrapper().createSessionPositionInfoForBundling()); getPlayerWrapper().createSessionPositionInfoForBundling());
dispatchOnPlayerInfoChanged(excludeTimeline); dispatchOnPlayerInfoChanged(playerInfo, excludeTimeline);
excludeTimeline = true; excludeTimeline = true;
} else { } else {
throw new IllegalStateException("Invalid message what=" + msg.what); throw new IllegalStateException("Invalid message what=" + msg.what);
......
...@@ -44,6 +44,7 @@ import androidx.media3.common.Timeline.Window; ...@@ -44,6 +44,7 @@ import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.BundleableUtil;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
...@@ -271,6 +272,9 @@ import java.util.List; ...@@ -271,6 +272,9 @@ import java.util.List;
} }
public PlayerInfo build() { public PlayerInfo build() {
Assertions.checkState(
timeline.isEmpty()
|| sessionPositionInfo.positionInfo.mediaItemIndex < timeline.getWindowCount());
return new PlayerInfo( return new PlayerInfo(
playerError, playerError,
mediaItemTransitionReason, mediaItemTransitionReason,
...@@ -344,7 +348,7 @@ import java.util.List; ...@@ -344,7 +348,7 @@ import java.util.List;
MediaMetadata.EMPTY, MediaMetadata.EMPTY,
/* seekBackIncrementMs= */ 0, /* seekBackIncrementMs= */ 0,
/* seekForwardIncrementMs= */ 0, /* seekForwardIncrementMs= */ 0,
/* maxSeekToPreviousPosition= */ 0, /* maxSeekToPreviousPositionMs= */ 0,
TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT); TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT);
@Nullable public final PlaybackException playerError; @Nullable public final PlaybackException playerError;
...@@ -430,11 +434,6 @@ import java.util.List; ...@@ -430,11 +434,6 @@ import java.util.List;
} }
@CheckResult @CheckResult
public PlayerInfo copyWithSessionPositionInfo(SessionPositionInfo sessionPositionInfo) {
return new Builder(this).setSessionPositionInfo(sessionPositionInfo).build();
}
@CheckResult
public PlayerInfo copyWithPlaybackState( public PlayerInfo copyWithPlaybackState(
@Player.State int playbackState, @Nullable PlaybackException playerError) { @Player.State int playbackState, @Nullable PlaybackException playerError) {
return new Builder(this) return new Builder(this)
...@@ -455,6 +454,11 @@ import java.util.List; ...@@ -455,6 +454,11 @@ import java.util.List;
} }
@CheckResult @CheckResult
public PlayerInfo copyWithPlaybackParameters(PlaybackParameters playbackParameters) {
return new Builder(this).setPlaybackParameters(playbackParameters).build();
}
@CheckResult
public PlayerInfo copyWithPositionInfos( public PlayerInfo copyWithPositionInfos(
PositionInfo oldPositionInfo, PositionInfo oldPositionInfo,
PositionInfo newPositionInfo, PositionInfo newPositionInfo,
...@@ -467,8 +471,8 @@ import java.util.List; ...@@ -467,8 +471,8 @@ import java.util.List;
} }
@CheckResult @CheckResult
public PlayerInfo copyWithPlaybackParameters(PlaybackParameters playbackParameters) { public PlayerInfo copyWithSessionPositionInfo(SessionPositionInfo sessionPositionInfo) {
return new Builder(this).setPlaybackParameters(playbackParameters).build(); return new Builder(this).setSessionPositionInfo(sessionPositionInfo).build();
} }
@CheckResult @CheckResult
...@@ -477,14 +481,23 @@ import java.util.List; ...@@ -477,14 +481,23 @@ import java.util.List;
} }
@CheckResult @CheckResult
public PlayerInfo copyWithTimeline(Timeline timeline, int windowIndex) { public PlayerInfo copyWithTimelineAndSessionPositionInfo(
Timeline timeline, SessionPositionInfo sessionPositionInfo) {
return new Builder(this)
.setTimeline(timeline)
.setSessionPositionInfo(sessionPositionInfo)
.build();
}
@CheckResult
public PlayerInfo copyWithTimelineAndMediaItemIndex(Timeline timeline, int mediaItemIndex) {
return new Builder(this) return new Builder(this)
.setTimeline(timeline) .setTimeline(timeline)
.setSessionPositionInfo( .setSessionPositionInfo(
new SessionPositionInfo( new SessionPositionInfo(
new PositionInfo( new PositionInfo(
sessionPositionInfo.positionInfo.windowUid, sessionPositionInfo.positionInfo.windowUid,
windowIndex, mediaItemIndex,
sessionPositionInfo.positionInfo.mediaItem, sessionPositionInfo.positionInfo.mediaItem,
sessionPositionInfo.positionInfo.periodUid, sessionPositionInfo.positionInfo.periodUid,
sessionPositionInfo.positionInfo.periodIndex, sessionPositionInfo.positionInfo.periodIndex,
......
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