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
This commit is contained in:
parent
9369348d6f
commit
dee83cc7db
@ -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;
|
|||||||
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;
|
|||||||
+ " 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;
|
|||||||
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;
|
|||||||
(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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|||||||
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;
|
||||||
@ -429,11 +433,6 @@ import java.util.List;
|
|||||||
return new Builder(this).setPlayerError(playerError).build();
|
return new Builder(this).setPlayerError(playerError).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
|
||||||
public PlayerInfo copyWithSessionPositionInfo(SessionPositionInfo sessionPositionInfo) {
|
|
||||||
return new Builder(this).setSessionPositionInfo(sessionPositionInfo).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public PlayerInfo copyWithPlaybackState(
|
public PlayerInfo copyWithPlaybackState(
|
||||||
@Player.State int playbackState, @Nullable PlaybackException playerError) {
|
@Player.State int playbackState, @Nullable PlaybackException playerError) {
|
||||||
@ -454,6 +453,11 @@ import java.util.List;
|
|||||||
return new Builder(this).setIsLoading(isLoading).build();
|
return new Builder(this).setIsLoading(isLoading).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CheckResult
|
||||||
|
public PlayerInfo copyWithPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
|
return new Builder(this).setPlaybackParameters(playbackParameters).build();
|
||||||
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public PlayerInfo copyWithPositionInfos(
|
public PlayerInfo copyWithPositionInfos(
|
||||||
PositionInfo oldPositionInfo,
|
PositionInfo oldPositionInfo,
|
||||||
@ -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;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user