use of androidx.media3.common.TrackSelectionArray in project media by androidx.
the class CastPlayer method updateTracksAndSelectionsAndNotifyIfChanged.
/**
* Updates the internal tracks and selection and returns whether they have changed.
*/
private boolean updateTracksAndSelectionsAndNotifyIfChanged() {
if (remoteMediaClient == null) {
// There is no session. We leave the state of the player as it is now.
return false;
}
MediaStatus mediaStatus = getMediaStatus();
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
boolean hasChanged = !currentTrackGroups.isEmpty();
currentTrackGroups = TrackGroupArray.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
return hasChanged;
}
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
}
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
TracksInfo.TrackGroupInfo[] trackGroupInfos = new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
for (int i = 0; i < castMediaTracks.size(); i++) {
MediaTrack mediaTrack = castMediaTracks.get(i);
trackGroups[i] = new TrackGroup(/* id= */
Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
long id = mediaTrack.getId();
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
int rendererIndex = getRendererIndexForTrackType(trackType);
boolean supported = rendererIndex != C.INDEX_UNSET;
boolean selected = isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null;
if (selected) {
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
}
@C.FormatSupport int[] trackSupport = new int[] { supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE };
final boolean[] trackSelected = new boolean[] { selected };
trackGroupInfos[i] = new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected);
}
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
if (!newTrackGroups.equals(currentTrackGroups) || !newTrackSelections.equals(currentTrackSelection) || !newTracksInfo.equals(currentTracksInfo)) {
currentTrackSelection = newTrackSelections;
currentTrackGroups = newTrackGroups;
currentTracksInfo = newTracksInfo;
return true;
}
return false;
}
use of androidx.media3.common.TrackSelectionArray in project media by androidx.
the class ExoPlayerImpl method updatePlaybackInfo.
// Calling deprecated listeners.
@SuppressWarnings("deprecation")
private void updatePlaybackInfo(PlaybackInfo playbackInfo, @TimelineChangeReason int timelineChangeReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason, boolean seekProcessed, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, int oldMaskingMediaItemIndex) {
// Assign playback info immediately such that all getters return the right values, but keep
// snapshot of previous and new state so that listener invocations are triggered correctly.
PlaybackInfo previousPlaybackInfo = this.playbackInfo;
PlaybackInfo newPlaybackInfo = playbackInfo;
this.playbackInfo = playbackInfo;
Pair<Boolean, Integer> mediaItemTransitionInfo = evaluateMediaItemTransitionReason(newPlaybackInfo, previousPlaybackInfo, positionDiscontinuity, positionDiscontinuityReason, !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline));
boolean mediaItemTransitioned = mediaItemTransitionInfo.first;
int mediaItemTransitionReason = mediaItemTransitionInfo.second;
MediaMetadata newMediaMetadata = mediaMetadata;
@Nullable MediaItem mediaItem = null;
if (mediaItemTransitioned) {
if (!newPlaybackInfo.timeline.isEmpty()) {
int windowIndex = newPlaybackInfo.timeline.getPeriodByUid(newPlaybackInfo.periodId.periodUid, period).windowIndex;
mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem;
}
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
}
if (mediaItemTransitioned || !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(newPlaybackInfo.staticMetadata).build();
newMediaMetadata = buildUpdatedMediaMetadata();
}
boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata);
mediaMetadata = newMediaMetadata;
boolean playWhenReadyChanged = previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady;
boolean playbackStateChanged = previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState;
if (playbackStateChanged || playWhenReadyChanged) {
updateWakeAndWifiLock();
}
boolean isLoadingChanged = previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading;
if (isLoadingChanged) {
updatePriorityTaskManagerForIsLoadingChange(newPlaybackInfo.isLoading);
}
if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) {
listeners.queueEvent(Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason));
}
if (positionDiscontinuity) {
PositionInfo previousPositionInfo = getPreviousPositionInfo(positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex);
PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs);
listeners.queueEvent(Player.EVENT_POSITION_DISCONTINUITY, listener -> {
listener.onPositionDiscontinuity(positionDiscontinuityReason);
listener.onPositionDiscontinuity(previousPositionInfo, positionInfo, positionDiscontinuityReason);
});
}
if (mediaItemTransitioned) {
@Nullable final MediaItem finalMediaItem = mediaItem;
listeners.queueEvent(Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason));
}
if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) {
listeners.queueEvent(Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError));
if (newPlaybackInfo.playbackError != null) {
listeners.queueEvent(Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
}
}
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection = new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo));
}
if (metadataChanged) {
final MediaMetadata finalMediaMetadata = mediaMetadata;
listeners.queueEvent(EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata));
}
if (isLoadingChanged) {
listeners.queueEvent(Player.EVENT_IS_LOADING_CHANGED, listener -> {
listener.onLoadingChanged(newPlaybackInfo.isLoading);
listener.onIsLoadingChanged(newPlaybackInfo.isLoading);
});
}
if (playbackStateChanged || playWhenReadyChanged) {
listeners.queueEvent(/* eventFlag= */
C.INDEX_UNSET, listener -> listener.onPlayerStateChanged(newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState));
}
if (playbackStateChanged) {
listeners.queueEvent(Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState));
}
if (playWhenReadyChanged) {
listeners.queueEvent(Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged(newPlaybackInfo.playWhenReady, playWhenReadyChangeReason));
}
if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) {
listeners.queueEvent(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged(newPlaybackInfo.playbackSuppressionReason));
}
if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) {
listeners.queueEvent(Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
}
if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) {
listeners.queueEvent(Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters));
}
if (seekProcessed) {
listeners.queueEvent(/* eventFlag= */
C.INDEX_UNSET, Listener::onSeekProcessed);
}
updateAvailableCommands();
listeners.flushEvents();
if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) {
for (AudioOffloadListener listener : audioOffloadListeners) {
listener.onExperimentalOffloadSchedulingEnabledChanged(newPlaybackInfo.offloadSchedulingEnabled);
}
}
if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) {
for (AudioOffloadListener listener : audioOffloadListeners) {
listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload);
}
}
}
use of androidx.media3.common.TrackSelectionArray in project media by androidx.
the class EventLogger method onTracksChanged.
@Override
public void onTracksChanged(EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
MappedTrackInfo mappedTrackInfo = trackSelector != null ? trackSelector.getCurrentMappedTrackInfo() : null;
if (mappedTrackInfo == null) {
logd(eventTime, "tracks", "[]");
return;
}
logd("tracks [" + getEventTimeString(eventTime));
// Log tracks associated to renderers.
int rendererCount = mappedTrackInfo.getRendererCount();
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
TrackSelection trackSelection = trackSelections.get(rendererIndex);
if (rendererTrackGroups.length == 0) {
logd(" " + mappedTrackInfo.getRendererName(rendererIndex) + " []");
} else {
logd(" " + mappedTrackInfo.getRendererName(rendererIndex) + " [");
for (int groupIndex = 0; groupIndex < rendererTrackGroups.length; groupIndex++) {
TrackGroup trackGroup = rendererTrackGroups.get(groupIndex);
String adaptiveSupport = getAdaptiveSupportString(trackGroup.length, mappedTrackInfo.getAdaptiveSupport(rendererIndex, groupIndex, /* includeCapabilitiesExceededTracks= */
false));
logd(" Group:" + trackGroup.id + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
@Capabilities int capabilities = mappedTrackInfo.getCapabilities(rendererIndex, groupIndex, trackIndex);
String formatSupport = getFormatSupportString(getFormatSupport(capabilities));
String hardwareAccelerationSupport = getHardwareAccelerationSupport(capabilities) == HARDWARE_ACCELERATION_SUPPORTED ? ", accelerated=YES" : "";
String decoderSupport = getDecoderSupport(capabilities) == DECODER_SUPPORT_FALLBACK ? ", fallback=YES" : "";
logd(" " + status + " Track:" + trackIndex + ", " + Format.toLogString(trackGroup.getFormat(trackIndex)) + ", supported=" + formatSupport + hardwareAccelerationSupport + decoderSupport);
}
logd(" ]");
}
// Log metadata for at most one of the tracks selected for the renderer.
if (trackSelection != null) {
for (int selectionIndex = 0; selectionIndex < trackSelection.length(); selectionIndex++) {
Metadata metadata = trackSelection.getFormat(selectionIndex).metadata;
if (metadata != null) {
logd(" Metadata [");
printMetadata(metadata, " ");
logd(" ]");
break;
}
}
}
logd(" ]");
}
}
// Log tracks not associated with a renderer.
TrackGroupArray unassociatedTrackGroups = mappedTrackInfo.getUnmappedTrackGroups();
if (unassociatedTrackGroups.length > 0) {
logd(" Unmapped [");
for (int groupIndex = 0; groupIndex < unassociatedTrackGroups.length; groupIndex++) {
logd(" Group:" + groupIndex + " [");
TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex);
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(false);
String formatSupport = getFormatSupportString(C.FORMAT_UNSUPPORTED_TYPE);
logd(" " + status + " Track:" + trackIndex + ", " + Format.toLogString(trackGroup.getFormat(trackIndex)) + ", supported=" + formatSupport);
}
logd(" ]");
}
logd(" ]");
}
logd("]");
}
use of androidx.media3.common.TrackSelectionArray in project media by androidx.
the class ExoPlayerTest method errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState.
@Test
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
FakeMediaSource source1 = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
FakeMediaSource source2 = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.AUDIO_FORMAT);
AtomicInteger audioRendererEnableCount = new AtomicInteger(0);
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException {
if (audioRendererEnableCount.incrementAndGet() == 2) {
// Fail when enabling the renderer for the second time during the playlist update.
throw createRendererException(new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT, PlaybackException.ERROR_CODE_UNSPECIFIED);
}
}
};
AtomicReference<Timeline> timelineAfterError = new AtomicReference<>();
AtomicReference<TracksInfo> trackInfosAfterError = new AtomicReference<>();
AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
AtomicInteger mediaItemIndexAfterError = new AtomicInteger();
ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG).executeRunnable(new PlayerRunnable() {
@Override
public void run(ExoPlayer player) {
player.addAnalyticsListener(new AnalyticsListener() {
@Override
public void onPlayerError(EventTime eventTime, PlaybackException error) {
timelineAfterError.set(player.getCurrentTimeline());
trackInfosAfterError.set(player.getCurrentTracksInfo());
trackSelectionsAfterError.set(player.getCurrentTrackSelections());
mediaItemIndexAfterError.set(player.getCurrentMediaItemIndex());
}
});
}
}).pause().waitForIsLoading(true).waitForIsLoading(false).removeMediaItem(0).build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder(context).setMediaSources(source1, source2).setActionSchedule(actionSchedule).setRenderers(videoRenderer, audioRenderer).build();
assertThrows(ExoPlaybackException.class, () -> testRunner.start(/* doPrepare= */
true).blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS));
assertThat(timelineAfterError.get().getWindowCount()).isEqualTo(1);
assertThat(mediaItemIndexAfterError.get()).isEqualTo(0);
assertThat(trackInfosAfterError.get().getTrackGroupInfos()).hasSize(1);
assertThat(trackInfosAfterError.get().getTrackGroupInfos().get(0).getTrackGroup().getFormat(0)).isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
// Video renderer.
assertThat(trackSelectionsAfterError.get().get(0)).isNull();
// Audio renderer.
assertThat(trackSelectionsAfterError.get().get(1)).isNotNull();
}
use of androidx.media3.common.TrackSelectionArray in project media by androidx.
the class ExoPlayerTest method updateTrackSelectorThenSeekToUnpreparedPeriod_returnsEmptyTrackGroups.
@Test
public void updateTrackSelectorThenSeekToUnpreparedPeriod_returnsEmptyTrackGroups() throws Exception {
// Use unset duration to prevent pre-loading of the second window.
Timeline timelineUnsetDuration = new FakeTimeline(new TimelineWindowDefinition(/* isSeekable= */
true, /* isDynamic= */
false, /* durationUs= */
C.TIME_UNSET));
Timeline timelineSetDuration = new FakeTimeline();
MediaSource mediaSource = new ConcatenatingMediaSource(new FakeMediaSource(timelineUnsetDuration, ExoPlayerTestRunner.VIDEO_FORMAT), new FakeMediaSource(timelineSetDuration, ExoPlayerTestRunner.AUDIO_FORMAT));
ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG).pause().waitForPlaybackState(Player.STATE_READY).seek(/* mediaItemIndex= */
1, /* positionMs= */
0).waitForPendingPlayerCommands().play().build();
List<TrackGroupArray> trackGroupsList = new ArrayList<>();
List<TrackSelectionArray> trackSelectionsList = new ArrayList<>();
new ExoPlayerTestRunner.Builder(context).setMediaSources(mediaSource).setSupportedFormats(ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT).setActionSchedule(actionSchedule).setPlayerListener(new Player.Listener() {
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
trackGroupsList.add(trackGroups);
trackSelectionsList.add(trackSelections);
}
}).build().start().blockUntilEnded(TIMEOUT_MS);
assertThat(trackGroupsList).hasSize(3);
// First track groups of the 1st period are reported.
// Then the seek to an unprepared period will result in empty track groups and selections being
// returned.
// Then the track groups of the 2nd period are reported.
assertThat(trackGroupsList.get(0).get(0).getFormat(0)).isEqualTo(ExoPlayerTestRunner.VIDEO_FORMAT);
assertThat(trackGroupsList.get(1)).isEqualTo(TrackGroupArray.EMPTY);
assertThat(trackSelectionsList.get(1).get(0)).isNull();
assertThat(trackGroupsList.get(2).get(0).getFormat(0)).isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
}
Aggregations