Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show video track selectors on PlayerControlView #972

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
* IMA extension:
* Session:
* UI:
* Add video track selection functionality to settings pop-up menu in
`PlayerControlView`
([#972](https://github.com/androidx/media/pull/972)).
* Downloads:
* OkHttp Extension:
* Cronet Extension:
Expand Down
108 changes: 106 additions & 2 deletions libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,11 @@ public interface OnFullScreenModeChangedListener {
// LINT.IfChange(playback_speeds)
private static final float[] PLAYBACK_SPEEDS =
new float[] {0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 2f};
// LINT.ThenChange("../../../../res/values/strings.xml:playback_speeds")

private static final int SETTINGS_PLAYBACK_SPEED_POSITION = 0;
private static final int SETTINGS_AUDIO_TRACK_SELECTION_POSITION = 1;
private static final int SETTINGS_VIDEO_TRACK_SELECTION_POSITION = 2;

private final PlayerControlViewLayoutManager controlViewLayoutManager;
private final Resources resources;
Expand All @@ -276,6 +278,7 @@ public interface OnFullScreenModeChangedListener {
private final SettingsAdapter settingsAdapter;
private final PlaybackSpeedAdapter playbackSpeedAdapter;
private final TextTrackSelectionAdapter textTrackSelectionAdapter;
private final VideoTrackSelectionAdapter videoTrackSelectionAdapter;
private final AudioTrackSelectionAdapter audioTrackSelectionAdapter;
// TODO(insun): Add setTrackNameProvider to use customized track name provider.
private final TrackNameProvider trackNameProvider;
Expand All @@ -298,6 +301,7 @@ public interface OnFullScreenModeChangedListener {
@Nullable private final View settingsButton;
@Nullable private final View playbackSpeedButton;
@Nullable private final View audioTrackButton;
@Nullable private final View videoTrackButton;
@Nullable private final TextView durationView;
@Nullable private final TextView positionView;
@Nullable private final TimeBar timeBar;
Expand Down Expand Up @@ -469,6 +473,11 @@ public PlayerControlView(
audioTrackButton.setOnClickListener(componentListener);
}

videoTrackButton = findViewById(R.id.exo_video_track);
if (videoTrackButton != null) {
videoTrackButton.setOnClickListener(componentListener);
}

TimeBar customTimeBar = findViewById(R.id.exo_progress);
View timeBarPlaceholder = findViewById(R.id.exo_progress_placeholder);
if (customTimeBar != null) {
Expand Down Expand Up @@ -546,16 +555,23 @@ public PlayerControlView(
controlViewLayoutManager = new PlayerControlViewLayoutManager(this);
controlViewLayoutManager.setAnimationEnabled(animationEnabled);

String[] settingTexts = new String[2];
Drawable[] settingIcons = new Drawable[2];
String[] settingTexts = new String[3];
Drawable[] settingIcons = new Drawable[3];
settingTexts[SETTINGS_PLAYBACK_SPEED_POSITION] =
resources.getString(R.string.exo_controls_playback_speed);
settingIcons[SETTINGS_PLAYBACK_SPEED_POSITION] =
getDrawable(context, resources, R.drawable.exo_styled_controls_speed);

settingTexts[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] =
resources.getString(R.string.exo_track_selection_title_audio);
settingIcons[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] =
getDrawable(context, resources, R.drawable.exo_styled_controls_audiotrack);

settingTexts[SETTINGS_VIDEO_TRACK_SELECTION_POSITION] =
resources.getString(R.string.exo_track_selection_title_video);
settingIcons[SETTINGS_VIDEO_TRACK_SELECTION_POSITION] =
getDrawable(context, resources, R.drawable.exo_styled_controls_videotrack);

settingsAdapter = new SettingsAdapter(settingTexts, settingIcons);
settingsWindowMargin = resources.getDimensionPixelSize(R.dimen.exo_settings_offset);
settingsView =
Expand Down Expand Up @@ -585,6 +601,7 @@ public PlayerControlView(
resources.getString(R.string.exo_controls_cc_disabled_description);
textTrackSelectionAdapter = new TextTrackSelectionAdapter();
audioTrackSelectionAdapter = new AudioTrackSelectionAdapter();
videoTrackSelectionAdapter = new VideoTrackSelectionAdapter();
playbackSpeedAdapter =
new PlaybackSpeedAdapter(
resources.getStringArray(R.array.exo_controls_playback_speeds), PLAYBACK_SPEEDS);
Expand Down Expand Up @@ -1153,13 +1170,18 @@ private void updateTrackLists() {
private void initTrackSelectionAdapter() {
textTrackSelectionAdapter.clear();
audioTrackSelectionAdapter.clear();
videoTrackSelectionAdapter.clear();

if (player == null
|| !player.isCommandAvailable(COMMAND_GET_TRACKS)
|| !player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) {
return;
}

Tracks tracks = player.getCurrentTracks();
audioTrackSelectionAdapter.init(gatherSupportedTrackInfosOfType(tracks, C.TRACK_TYPE_AUDIO));
videoTrackSelectionAdapter.init(gatherSupportedTrackInfosOfType(tracks, C.TRACK_TYPE_VIDEO));

if (controlViewLayoutManager.getShowButton(subtitleButton)) {
textTrackSelectionAdapter.init(gatherSupportedTrackInfosOfType(tracks, C.TRACK_TYPE_TEXT));
} else {
Expand Down Expand Up @@ -1439,6 +1461,8 @@ private void onSettingViewClicked(int position) {
displaySettingsWindow(playbackSpeedAdapter, checkNotNull(settingsButton));
} else if (position == SETTINGS_AUDIO_TRACK_SELECTION_POSITION) {
displaySettingsWindow(audioTrackSelectionAdapter, checkNotNull(settingsButton));
} else if (position == SETTINGS_VIDEO_TRACK_SELECTION_POSITION) {
displaySettingsWindow(videoTrackSelectionAdapter, checkNotNull(settingsButton));
} else {
settingsWindow.dismiss();
}
Expand Down Expand Up @@ -1745,6 +1769,9 @@ public void onClick(View view) {
} else if (audioTrackButton == view) {
controlViewLayoutManager.removeHideCallbacks();
displaySettingsWindow(audioTrackSelectionAdapter, audioTrackButton);
} else if (videoTrackButton == view) {
controlViewLayoutManager.removeHideCallbacks();
displaySettingsWindow(videoTrackSelectionAdapter, videoTrackButton);
} else if (subtitleButton == view) {
controlViewLayoutManager.removeHideCallbacks();
displaySettingsWindow(textTrackSelectionAdapter, subtitleButton);
Expand Down Expand Up @@ -2071,6 +2098,83 @@ public void init(List<TrackInformation> trackInformations) {
}
}

private final class VideoTrackSelectionAdapter extends TrackSelectionAdapter {

@Override
public void onBindViewHolderAtZeroPosition(SubSettingViewHolder holder) {
// Video track selection option includes "Auto" at the top.
holder.textView.setText(R.string.exo_track_selection_auto);
// hasSelectionOverride is true means there is an explicit track selection, not "Auto".
TrackSelectionParameters parameters = checkNotNull(player).getTrackSelectionParameters();
boolean hasSelectionOverride = hasSelectionOverride(parameters);
holder.checkView.setVisibility(hasSelectionOverride ? INVISIBLE : VISIBLE);
holder.itemView.setOnClickListener(
v -> {
if (player == null
|| !player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)) {
return;
}

TrackSelectionParameters trackSelectionParameters =
player.getTrackSelectionParameters();

castNonNull(player)
.setTrackSelectionParameters(
trackSelectionParameters
.buildUpon()
.clearOverridesOfType(C.TRACK_TYPE_VIDEO)
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ false)
.build());

settingsAdapter.setSubTextAtPosition(
SETTINGS_VIDEO_TRACK_SELECTION_POSITION,
getResources().getString(R.string.exo_track_selection_auto));

settingsWindow.dismiss();
});
}

private boolean hasSelectionOverride(TrackSelectionParameters trackSelectionParameters) {
for (int i = 0; i < tracks.size(); i++) {
TrackGroup trackGroup = tracks.get(i).trackGroup.getMediaTrackGroup();
if (trackSelectionParameters.overrides.containsKey(trackGroup)) {
return true;
}
}
return false;
}

@Override
public void onTrackSelection(String subtext) {
settingsAdapter.setSubTextAtPosition(SETTINGS_VIDEO_TRACK_SELECTION_POSITION, subtext);
}

@Override
public void init(List<TrackInformation> trackInformations) {
this.tracks = trackInformations;
// Update subtext in settings menu with current video track selection.
TrackSelectionParameters params = checkNotNull(player).getTrackSelectionParameters();
if (trackInformations.isEmpty()) {
settingsAdapter.setSubTextAtPosition(
SETTINGS_VIDEO_TRACK_SELECTION_POSITION,
getResources().getString(R.string.exo_track_selection_none));
} else if (!hasSelectionOverride(params)) {
settingsAdapter.setSubTextAtPosition(
SETTINGS_VIDEO_TRACK_SELECTION_POSITION,
getResources().getString(R.string.exo_track_selection_auto));
} else {
for (int i = 0; i < trackInformations.size(); i++) {
TrackInformation track = trackInformations.get(i);
if (track.isSelected()) {
settingsAdapter.setSubTextAtPosition(
SETTINGS_VIDEO_TRACK_SELECTION_POSITION, track.trackName);
break;
}
}
}
}
}

private abstract class TrackSelectionAdapter extends RecyclerView.Adapter<SubSettingViewHolder> {

protected List<TrackInformation> tracks;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z" />
</vector>
1 change: 1 addition & 0 deletions libraries/ui/src/main/res/values/drawables.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@
<drawable name="exo_styled_controls_settings">@drawable/exo_ic_settings</drawable>
<drawable name="exo_styled_controls_check">@drawable/exo_ic_check</drawable>
<drawable name="exo_styled_controls_audiotrack">@drawable/exo_ic_audiotrack</drawable>
<drawable name="exo_styled_controls_videotrack">@drawable/exo_ic_videotrack</drawable>
<drawable name="exo_styled_controls_speed">@drawable/exo_ic_speed</drawable>
</resources>
1 change: 1 addition & 0 deletions libraries/ui/src/main/res/values/ids.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<item name="exo_fullscreen" type="id"/>
<item name="exo_playback_speed" type="id"/>
<item name="exo_audio_track" type="id"/>
<item name="exo_video_track" type="id"/>
<item name="exo_settings" type="id"/>
<item name="exo_controls_background" type="id"/>
<item name="exo_basic_controls" type="id"/>
Expand Down