Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
Expand Down Expand Up @@ -310,12 +311,18 @@ public final class Player implements
private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79;
private static final int POPUP_MENU_ID_CAPTION = 89;
private static final int POPUP_MENU_ID_AUDIO_TRACK = 99;
private static final int POPUP_MENU_ID_ASPECT_RATIO = 109;

private boolean isSomePopupMenuVisible = false;
private PopupMenu qualityPopupMenu;
private PopupMenu playbackSpeedPopupMenu;
private PopupMenu captionPopupMenu;
private PopupMenu audioTrackPopupMenu;
private PopupMenu aspectRatioPopupMenu;

// Aspect ratio forced by the user, 0 means "auto" (use the video's own aspect ratio)
private float forcedAspectRatio;
private float videoNaturalAspectRatio;

/*//////////////////////////////////////////////////////////////////////////
// Popup player
Expand Down Expand Up @@ -497,6 +504,9 @@ private void initViews(@NonNull final PlayerBinding playerBinding) {
binding.resizeTextView
.setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode()));

binding.aspectRatioTextView
.setText(PlayerHelper.aspectRatioNameOf(context, forcedAspectRatio));

binding.playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
binding.playbackSeekBar.getProgressDrawable()
Expand All @@ -509,6 +519,8 @@ private void initViews(@NonNull final PlayerBinding playerBinding) {
playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed);
captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView);
audioTrackPopupMenu = new PopupMenu(themeWrapper, binding.audioTrackTextView);
aspectRatioPopupMenu = new PopupMenu(themeWrapper, binding.aspectRatioTextView);
buildAspectRatioMenu();

binding.progressBarLoadingPanel.getIndeterminateDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
Expand Down Expand Up @@ -570,6 +582,7 @@ private void initListeners() {
binding.captionTextView.setOnClickListener(this);
binding.audioTrackTextView.setOnClickListener(this);
binding.resizeTextView.setOnClickListener(this);
binding.aspectRatioTextView.setOnClickListener(this);
binding.playbackLiveSync.setOnClickListener(this);

playerGestureListener = new PlayerGestureListener(this, service);
Expand Down Expand Up @@ -1145,6 +1158,7 @@ private void setupElementsVisibility() {
binding.fullScreenButton.setVisibility(View.VISIBLE);
binding.screenRotationButton.setVisibility(View.GONE);
binding.resizeTextView.setVisibility(View.GONE);
binding.aspectRatioTextView.setVisibility(View.GONE);
binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE);
binding.queueButton.setVisibility(View.GONE);
binding.segmentsButton.setVisibility(View.GONE);
Expand All @@ -1171,6 +1185,7 @@ private void setupElementsVisibility() {
binding.fullScreenButton.setVisibility(View.GONE);
setupScreenRotationButton();
binding.resizeTextView.setVisibility(View.VISIBLE);
binding.aspectRatioTextView.setVisibility(View.VISIBLE);
binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE);
binding.moreOptionsButton.setVisibility(View.VISIBLE);
binding.topControls.setOrientation(LinearLayout.VERTICAL);
Expand Down Expand Up @@ -1237,6 +1252,7 @@ private void setupElementsSize() {
binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
binding.playbackSpeed.setMinimumWidth(buttonsMinWidth);
binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
binding.aspectRatioTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
}

private void showHideKodiButton() {
Expand Down Expand Up @@ -3529,6 +3545,11 @@ private void onMetadataChanged(@NonNull final StreamInfo info) {
Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName());
}

// a forced aspect ratio is a per-video correction, don't carry it over to the next one
if (forcedAspectRatio > 0) {
setPlaybackAspectRatio(0.0f);
}

initThumbnail(info.getThumbnailUrl());
registerStreamViewed();
updateStreamRelatedViews();
Expand Down Expand Up @@ -4178,6 +4199,9 @@ private void closeAllPopupMenus() {
if (captionPopupMenu != null) {
captionPopupMenu.dismiss();
}
if (aspectRatioPopupMenu != null) {
aspectRatioPopupMenu.dismiss();
}
isSomePopupMenuVisible = false;
}
//endregion
Expand Down Expand Up @@ -4363,6 +4387,8 @@ public void onClick(final View v) {
}
if (v.getId() == binding.resizeTextView.getId()) {
onResizeClicked();
} else if (v.getId() == binding.aspectRatioTextView.getId()) {
onAspectRatioClicked();
} else if (v.getId() == binding.captionTextView.getId()) {
onCaptionClicked();
} else if (v.getId() == binding.audioTrackTextView.getId()) {
Expand Down Expand Up @@ -4603,6 +4629,85 @@ void onResizeClicked() {
}
}

private void buildAspectRatioMenu() {
if (aspectRatioPopupMenu == null) {
return;
}
aspectRatioPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_ASPECT_RATIO);
aspectRatioPopupMenu.setOnDismissListener(this);

final MenuItem autoItem = aspectRatioPopupMenu.getMenu().add(POPUP_MENU_ID_ASPECT_RATIO,
0, Menu.NONE, R.string.aspect_ratio_auto);
autoItem.setOnMenuItemClickListener(menuItem -> {
setPlaybackAspectRatio(0.0f);
return true;
});

for (int i = 0; i < PlayerHelper.ASPECT_RATIO_VALUES.length; i++) {
final float ratio = PlayerHelper.ASPECT_RATIO_VALUES[i];
final MenuItem ratioItem = aspectRatioPopupMenu.getMenu().add(
POPUP_MENU_ID_ASPECT_RATIO, i + 1, Menu.NONE,
PlayerHelper.ASPECT_RATIO_LABELS[i]);
ratioItem.setOnMenuItemClickListener(menuItem -> {
setPlaybackAspectRatio(ratio);
return true;
});
}

final MenuItem customItem = aspectRatioPopupMenu.getMenu().add(POPUP_MENU_ID_ASPECT_RATIO,
PlayerHelper.ASPECT_RATIO_VALUES.length + 1, Menu.NONE,
R.string.aspect_ratio_custom);
customItem.setOnMenuItemClickListener(menuItem -> {
openCustomAspectRatioDialog();
return true;
});
}

private void onAspectRatioClicked() {
if (DEBUG) {
Log.d(TAG, "onAspectRatioClicked() called");
}
aspectRatioPopupMenu.show();
isSomePopupMenuVisible = true;
}

private void setPlaybackAspectRatio(final float aspectRatio) {
forcedAspectRatio = aspectRatio;
binding.aspectRatioTextView.setText(PlayerHelper.aspectRatioNameOf(context, aspectRatio));

final float effectiveRatio = aspectRatio > 0 ? aspectRatio : videoNaturalAspectRatio;
if (effectiveRatio > 0) {
binding.surfaceView.setAspectRatio(effectiveRatio);
}
}

private void openCustomAspectRatioDialog() {
final AppCompatActivity activity = getParentActivity();
if (activity == null) {
return;
}
final EditText input = new EditText(activity);
input.setHint(R.string.aspect_ratio_custom_hint);
input.setInputType(InputType.TYPE_CLASS_TEXT);
if (forcedAspectRatio > 0) {
input.setText(PlayerHelper.aspectRatioNameOf(context, forcedAspectRatio));
}
new AlertDialog.Builder(activity)
.setTitle(R.string.aspect_ratio_custom_title)
.setView(input)
.setPositiveButton(R.string.ok, (dialog, which) -> {
final float ratio = PlayerHelper.parseAspectRatio(input.getText().toString());
if (ratio > 0) {
setPlaybackAspectRatio(ratio);
} else {
Toast.makeText(context, R.string.aspect_ratio_invalid, Toast.LENGTH_SHORT)
.show();
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}

@Override // exoplayer listener
public void onVideoSizeChanged(@NonNull final VideoSize videoSize) {
if (DEBUG) {
Expand All @@ -4613,7 +4718,9 @@ public void onVideoSizeChanged(@NonNull final VideoSize videoSize) {
+ "pixelWidthHeightRatio = [" + videoSize.pixelWidthHeightRatio + "]");
}

binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height);
videoNaturalAspectRatio = ((float) videoSize.width) / videoSize.height;
binding.surfaceView.setAspectRatio(forcedAspectRatio > 0
? forcedAspectRatio : videoNaturalAspectRatio);
isVerticalVideo = videoSize.width < videoSize.height;

if (globalScreenOrientationLocked(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,56 @@ public static int nextRepeatMode(@RepeatMode final int repeatMode) {
}
}

/**
* Aspect ratios selectable in the player. Indices in both arrays match.
*/
public static final String[] ASPECT_RATIO_LABELS = {"1:1", "4:3", "16:9", "18:9", "21:9"};
public static final float[] ASPECT_RATIO_VALUES = {
1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 18.0f / 9.0f, 21.0f / 9.0f};

public static String aspectRatioNameOf(@NonNull final Context context,
final float aspectRatio) {
if (aspectRatio <= 0.0f) {
return context.getString(R.string.aspect_ratio_auto);
}
for (int i = 0; i < ASPECT_RATIO_VALUES.length; i++) {
if (Math.abs(ASPECT_RATIO_VALUES[i] - aspectRatio) < 0.001f) {
return ASPECT_RATIO_LABELS[i];
}
}
return String.format(Locale.US, "%.2f", aspectRatio);
}

/**
* Parses a user-entered aspect ratio like {@code "16:9"}, {@code "16/9"} or {@code "1.78"}.
*
* @return the ratio as a float, or {@code 0} if the input could not be parsed
*/
public static float parseAspectRatio(@Nullable final String input) {
if (input == null) {
return 0.0f;
}
final String trimmed = input.trim();
try {
final String[] parts = trimmed.split("[:/]");
if (parts.length == 2) {
final float width = Float.parseFloat(parts[0].trim());
final float height = Float.parseFloat(parts[1].trim());
if (width > 0 && height > 0) {
return width / height;
}
} else if (parts.length == 1) {
final float ratio = Float.parseFloat(trimmed.replace(',', '.'));
if (ratio > 0) {
return ratio;
}
}
} catch (final NumberFormatException ignored) {
// fall through to invalid
}
return 0.0f;
}

@ResizeMode
public static int retrieveResizeModeFromPrefs(final Player player) {
return player.getPrefs().getInt(player.getContext().getString(R.string.last_resize_mode),
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/layout/player.xml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,20 @@
tools:ignore="HardcodedText,RtlHardcoded"
tools:text="FIT" />

<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/aspectRatioTextView"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:minWidth="50dp"
android:padding="@dimen/player_main_buttons_padding"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:ignore="HardcodedText,RtlHardcoded"
tools:text="16:9" />

<FrameLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,11 @@
<string name="resize_fit">Fit</string>
<string name="resize_fill">Fill</string>
<string name="resize_zoom">Zoom</string>
<string name="aspect_ratio_auto">Auto</string>
<string name="aspect_ratio_custom">Custom…</string>
<string name="aspect_ratio_custom_title">Custom aspect ratio</string>
<string name="aspect_ratio_custom_hint">e.g. 16:9 or 1.78</string>
<string name="aspect_ratio_invalid">Invalid aspect ratio</string>
<string name="caption_auto_generated">Auto-generated</string>
<!-- Caption Settings -->
<string name="caption_setting_title">Captions</string>
Expand Down