From 089191b8c49788e9485c853ef966685e1244ceb0 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 12 Jul 2025 19:06:27 +0300 Subject: [PATCH 01/57] feat(YouTube): Add player button to change video quality --- .../quality/RememberVideoQualityPatch.java | 372 +++++++++++++++++- .../extension/youtube/settings/Settings.java | 1 + .../videoplayer/PlayerControlButton.java | 21 + .../videoplayer/VideoQualityDialogButton.java | 113 ++++++ .../video/quality/VideoQualityPatch.kt | 2 + .../button/VideoQualityDialogButtonPatch.kt | 62 +++ .../resources/addresources/values/strings.xml | 8 + .../revanced_video_quality_dialog_button.xml | 9 + ...evanced_video_quality_dialog_button_4k.xml | 9 + ...vanced_video_quality_dialog_button_fhd.xml | 9 + ...evanced_video_quality_dialog_button_hd.xml | 9 + ...vanced_video_quality_dialog_button_lhd.xml | 9 + ...vanced_video_quality_dialog_button_qhd.xml | 9 + .../youtube_controls_bottom_ui_container.xml | 29 ++ 14 files changed, 650 insertions(+), 12 deletions(-) create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_4k.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_qhd.xml create mode 100644 patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 82e75058b3..33725653c8 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -1,14 +1,29 @@ package app.revanced.extension.youtube.patches.playback.quality; import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.NetworkType; +import static app.revanced.extension.shared.Utils.dipToPixels; +import static app.revanced.extension.shared.Utils.showToastShort; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.*; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.widget.*; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; @@ -17,6 +32,7 @@ import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.ShortsPlayerState; +import app.revanced.extension.youtube.videoplayer.VideoQualityDialogButton; @SuppressWarnings("unused") public class RememberVideoQualityPatch { @@ -45,6 +61,38 @@ public class RememberVideoQualityPatch { @Nullable private static List videoQualities; + /** + * Mapping of filtered quality indices (used in dialog) to original quality indices. + */ + @Nullable + private static Map filteredToOriginalIndexMap; + + /** + * Quality interface and method for setting quality. + */ + private static Object qInterface; + private static String qIndexMethod; + + /** + * Tracks the last applied quality index. + */ + private static int lastAppliedQualityIndex = -1; // Initialize to -1 (invalid index) + + /** + * Getter for lastAppliedQualityIndex. + */ + public static int getLastAppliedQualityIndex() { + return lastAppliedQualityIndex; + } + + /** + * Getter for videoQualities. + */ + @Nullable + public static List getVideoQualities() { + return videoQualities; + } + private static boolean shouldRememberVideoQuality() { BooleanSetting preference = ShortsPlayerState.isOpen() ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED @@ -55,7 +103,7 @@ private static boolean shouldRememberVideoQuality() { private static void changeDefaultQuality(int defaultQuality) { String networkTypeMessage; boolean useShortsPreference = ShortsPlayerState.isOpen(); - if (Utils.getNetworkType() == NetworkType.MOBILE) { + if (Utils.getNetworkType() == Utils.NetworkType.MOBILE) { if (useShortsPreference) shortsQualityMobile.save(defaultQuality); else videoQualityMobile.save(defaultQuality); networkTypeMessage = str("revanced_remember_video_quality_mobile"); @@ -65,7 +113,7 @@ private static void changeDefaultQuality(int defaultQuality) { networkTypeMessage = str("revanced_remember_video_quality_wifi"); } if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) - Utils.showToastShort(str( + showToastShort(str( useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast", networkTypeMessage, (defaultQuality + "p") )); @@ -77,32 +125,49 @@ private static void changeDefaultQuality(int defaultQuality) { * @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2 * @param originalQualityIndex quality index to use, as chosen by YouTube */ - public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) { + public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, + Object qInterfaceArg, String qIndexMethodArg) { try { + // Store qInterface and qIndexMethod for use in dialog. + qInterface = qInterfaceArg; + qIndexMethod = qIndexMethodArg; + boolean useShortsPreference = ShortsPlayerState.isOpen(); - final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE + final int preferredQuality = Utils.getNetworkType() == Utils.NetworkType.MOBILE ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { + lastAppliedQualityIndex = originalQualityIndex; return originalQualityIndex; // Nothing to do. } if (videoQualities == null || videoQualities.size() != qualities.length) { videoQualities = new ArrayList<>(qualities.length); - for (Object streamQuality : qualities) { + filteredToOriginalIndexMap = new HashMap<>(); + Set seenQualities = new LinkedHashSet<>(); // Maintains insertion order. + int filteredIndex = 0; + + for (int i = 0; i < qualities.length; i++) { + Object streamQuality = qualities[i]; for (Field field : streamQuality.getClass().getFields()) { if (field.getType().isAssignableFrom(Integer.TYPE) && field.getName().length() <= 2) { - videoQualities.add(field.getInt(streamQuality)); + int quality = field.getInt(streamQuality); + if (quality == AUTOMATIC_VIDEO_QUALITY_VALUE || quality > 0) { + videoQualities.add(quality); + if (seenQualities.add(quality)) { + filteredToOriginalIndexMap.put(filteredIndex++, i); + } + } } } } - + // After changing videos the qualities can initially be for the prior video. // So if the qualities have changed an update is needed. qualityNeedsUpdating = true; - Logger.printDebug(() -> "VideoQualities: " + videoQualities); + Logger.printDebug(() -> "VideoQualities: " + videoQualities + ", IndexMap: " + filteredToOriginalIndexMap); } if (userChangedDefaultQuality) { @@ -110,10 +175,12 @@ public static int setVideoQuality(Object[] qualities, final int originalQualityI final int quality = videoQualities.get(userSelectedQualityIndex); Logger.printDebug(() -> "User changed default quality to: " + quality); changeDefaultQuality(quality); + lastAppliedQualityIndex = userSelectedQualityIndex; return userSelectedQualityIndex; } if (!qualityNeedsUpdating) { + lastAppliedQualityIndex = originalQualityIndex; return originalQualityIndex; } qualityNeedsUpdating = false; @@ -123,7 +190,7 @@ public static int setVideoQuality(Object[] qualities, final int originalQualityI int qualityIndexToUse = 0; int i = 0; for (Integer quality : videoQualities) { - if (quality <= preferredQuality && qualityToUse < quality) { + if (quality <= preferredQuality && qualityToUse < quality) { qualityToUse = quality; qualityIndexToUse = i; } @@ -148,9 +215,12 @@ public static int setVideoQuality(Object[] qualities, final int originalQualityI Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE); m.invoke(qInterface, qualityToUse); + lastAppliedQualityIndex = qualityIndexToUse; + VideoQualityDialogButton.updateButtonIcon(); return qualityIndexToUse; } catch (Exception ex) { Logger.printException(() -> "Failed to set quality", ex); + lastAppliedQualityIndex = originalQualityIndex; // Fallback to original index return originalQualityIndex; } } @@ -162,6 +232,7 @@ public static void userChangedQuality(int selectedQualityIndex) { if (shouldRememberVideoQuality()) { userSelectedQualityIndex = selectedQualityIndex; userChangedDefaultQuality = true; + VideoQualityDialogButton.updateButtonIcon(); } } @@ -172,6 +243,7 @@ public static void userChangedQualityInNewFlyout(int selectedQuality) { if (!shouldRememberVideoQuality()) return; changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080). + VideoQualityDialogButton.updateButtonIcon(); } /** @@ -181,5 +253,281 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; videoQualities = null; + filteredToOriginalIndexMap = null; + lastAppliedQualityIndex = -1; // Reset on new video. + } + + /** + * Shows a dialog with available video qualities, excluding duplicates. + */ + public static void showVideoQualityDialog(@NonNull Context context) { + try { + if (videoQualities == null || videoQualities.isEmpty()) { + showToastShort(str("revanced_video_quality_no_qualities_available")); + return; + } + + // Create dialog without a theme for custom appearance. + Dialog dialog = new Dialog(context); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setCanceledOnTouchOutside(true); + dialog.setCancelable(true); + + // Preset size constants. + final int dip4 = dipToPixels(4); // Height for handle bar. + final int dip5 = dipToPixels(5); // Padding for mainLayout. + final int dip6 = dipToPixels(6); // Bottom margin. + final int dip8 = dipToPixels(8); // Side padding. + final int dip20 = dipToPixels(20); // Margin below handle. + final int dip40 = dipToPixels(40); // Width for handle bar. + + // Create main vertical LinearLayout. + LinearLayout mainLayout = new LinearLayout(context); + mainLayout.setOrientation(LinearLayout.VERTICAL); + mainLayout.setPadding(dip5, dip8, dip5, dip8); + + // Set rounded rectangle background. + ShapeDrawable background = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(12), null, null)); + background.getPaint().setColor(Utils.getDialogBackgroundColor()); + mainLayout.setBackground(background); + + // Add handle bar at the top. + View handleBar = new View(context); + ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(4), null, null)); + handleBackground.getPaint().setColor(getAdjustedHandleBarBackgroundColor()); + handleBar.setBackground(handleBackground); + LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4); + handleParams.gravity = Gravity.CENTER_HORIZONTAL; + handleParams.setMargins(0, 0, 0, dip20); + handleBar.setLayoutParams(handleParams); + mainLayout.addView(handleBar); + + // Prepare dialog items, removing duplicates. + List qualityLabels = new ArrayList<>(); + List filteredQualities = new ArrayList<>(); + Set seenQualities = new LinkedHashSet<>(); + int filteredIndex = 0; + for (Integer quality : videoQualities) { + if (quality != AUTOMATIC_VIDEO_QUALITY_VALUE && seenQualities.add(quality)) { + String label = quality + "p"; + qualityLabels.add(label); + filteredQualities.add(quality); + filteredToOriginalIndexMap.put(filteredIndex++, videoQualities.indexOf(quality)); + } + } + + // Determine pre-selected quality index. + int selectedIndex; + if (lastAppliedQualityIndex >= 0 && lastAppliedQualityIndex < videoQualities.size()) { + int originalQuality = videoQualities.get(lastAppliedQualityIndex); + selectedIndex = filteredQualities.indexOf(originalQuality); + } else { + int preferredQuality = Utils.getNetworkType() == Utils.NetworkType.MOBILE + ? videoQualityMobile.get() + : videoQualityWifi.get(); + selectedIndex = filteredQualities.indexOf(preferredQuality); + if (selectedIndex < 0) selectedIndex = 0; + } + + // Create ListView for quality options. + ListView listView = new ListView(context); + CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); + adapter.setSelectedPosition(selectedIndex); + listView.setAdapter(adapter); + listView.setDivider(null); + listView.setPadding(0, 0, 0, 0); + + // Handle item click. + listView.setOnItemClickListener((parent, view, which, id) -> { + try { + int selectedQuality = filteredQualities.get(which); + int originalIndex = filteredToOriginalIndexMap.get(filteredQualities.indexOf(selectedQuality)); + if (qInterface != null && qIndexMethod != null) { + Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE); + m.invoke(qInterface, selectedQuality); + lastAppliedQualityIndex = originalIndex; + VideoQualityDialogButton.updateButtonIcon(); + Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " (original index: " + originalIndex + ")"); + } else { + Logger.printDebug(() -> "Cannot apply quality: qInterface or qIndexMethod is null"); + showToastShort(str("revanced_video_quality_apply_failed")); + } + + // Update saved setting if remembrance is enabled. + if (shouldRememberVideoQuality()) { + changeDefaultQuality(selectedQuality); + } + + showToastShort(str("revanced_video_quality_selected_toast", qualityLabels.get(which))); + dialog.dismiss(); + } catch (Exception ex) { + Logger.printException(() -> "Video quality selection failure", ex); + showToastShort(str("revanced_video_quality_apply_failed")); + } + }); + + // Add ListView to main layout. + LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + listViewParams.setMargins(0, 0, 0, dip5); + listView.setLayoutParams(listViewParams); + mainLayout.addView(listView); + + // Wrap mainLayout in another LinearLayout for side margins. + LinearLayout wrapperLayout = new LinearLayout(context); + wrapperLayout.setOrientation(LinearLayout.VERTICAL); + wrapperLayout.setPadding(dip8, 0, dip8, 0); + wrapperLayout.addView(mainLayout); + dialog.setContentView(wrapperLayout); + + // Configure dialog window. + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = Gravity.BOTTOM; + params.y = dip6; + int portraitWidth = context.getResources().getDisplayMetrics().widthPixels; + if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + portraitWidth = Math.min( + portraitWidth, + context.getResources().getDisplayMetrics().heightPixels); + } + params.width = portraitWidth; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(params); + window.setBackgroundDrawable(null); + } + + // Apply slide-in animation. + final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast"); + Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom"); + slideInABottomAnimation.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideInABottomAnimation); + + // Set touch listener for drag-to-dismiss. + mainLayout.setOnTouchListener(new View.OnTouchListener() { + final float dismissThreshold = Utils.dipToPixels(100); + float touchY; + float translationY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchY = event.getRawY(); + translationY = mainLayout.getTranslationY(); + return true; + case MotionEvent.ACTION_MOVE: + final float deltaY = event.getRawY() - touchY; + if (deltaY >= 0) { + mainLayout.setTranslationY(translationY + deltaY); + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mainLayout.getTranslationY() > dismissThreshold) { + final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels + - mainLayout.getTop(); + TranslateAnimation slideOut = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), remainingDistance); + slideOut.setDuration(fadeDurationFast); + slideOut.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + @Override + public void onAnimationEnd(Animation animation) { + dialog.dismiss(); + } + @Override + public void onAnimationRepeat(Animation animation) {} + }); + mainLayout.startAnimation(slideOut); + } else { + TranslateAnimation slideBack = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), 0); + slideBack.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideBack); + mainLayout.setTranslationY(0); + } + return true; + default: + return false; + } + } + }); + + dialog.show(); + } catch (Exception ex) { + Logger.printException(() -> "showVideoQualityDialog failure", ex); + } + } + + public static class CustomQualityAdapter extends ArrayAdapter { + private int selectedPosition = -1; + + public CustomQualityAdapter(@NonNull Context context, @NonNull List objects) { + super(context, 0, objects); + } + + public void setSelectedPosition(int position) { + this.selectedPosition = position; + notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate( + Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"), + parent, + false + ); + viewHolder = new ViewHolder(); + viewHolder.checkIcon = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon", "id") + ); + viewHolder.placeholder = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon_placeholder", "id") + ); + viewHolder.textView = convertView.findViewById( + Utils.getResourceIdentifier("revanced_item_text", "id") + ); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + + // Set text. + viewHolder.textView.setText(getItem(position)); + + // Show check icon for selected item. + boolean isSelected = position == selectedPosition; + viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); + viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE); + + return convertView; + } + + private static class ViewHolder { + ImageView checkIcon; + View placeholder; + TextView textView; + } + } + + /** + * Adjusts the HandleBar background color based on the current theme. + */ + public static int getAdjustedHandleBarBackgroundColor() { + final int baseColor = Utils.getDialogBackgroundColor(); + return Utils.isDarkModeEnabled() + ? Utils.adjustColorBrightness(baseColor, 1.25f) // Lighten for dark theme. + : Utils.adjustColorBrightness(baseColor, 0.9f); // Darken for light theme. } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 564dd5f31f..d3d5b1b8f4 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -171,6 +171,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE); public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE); public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE); + public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE); public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true); public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index 6d3c320171..5d3ae3a543 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -187,4 +187,25 @@ public void hide() { if (view != null) view.setVisibility(View.GONE); isVisible = false; } + + /** + * Sets the icon of the button. + * @param resourceName The name of the drawable resource. + */ + public void setIcon(String resourceName) { + try { + View button = buttonRef.get(); + if (button instanceof ImageView) { + int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); + if (resourceId != 0) { + ((ImageView) button).setImageResource(resourceId); + Logger.printDebug(() -> "Set button icon to: " + resourceName); + } else { + Logger.printDebug(() -> "Resource not found: " + resourceName); + } + } + } catch (Exception ex) { + Logger.printException(() -> "setIcon failure", ex); + } + } } \ No newline at end of file diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java new file mode 100644 index 0000000000..13d49f5804 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -0,0 +1,113 @@ +package app.revanced.extension.youtube.videoplayer; + +import android.view.View; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch; +import app.revanced.extension.youtube.settings.Settings; + +import java.util.List; + +import static app.revanced.extension.shared.StringRef.str; +import static app.revanced.extension.shared.Utils.showToastShort; + +@SuppressWarnings("unused") +public class VideoQualityDialogButton { + @Nullable + private static PlayerControlButton instance; + + /** + * Updates the button icon based on the current video quality. + */ + public static void updateButtonIcon() { + if (instance == null) return; + + try { + // Get the current quality. + int lastAppliedQualityIndex = RememberVideoQualityPatch.getLastAppliedQualityIndex(); + List videoQualities = RememberVideoQualityPatch.getVideoQualities(); + + if (videoQualities == null || lastAppliedQualityIndex < 0 || lastAppliedQualityIndex >= videoQualities.size()) { + // Default to a generic icon or "Auto". + instance.setIcon("revanced_video_quality_dialog_button"); + return; + } + + int quality = videoQualities.get(lastAppliedQualityIndex); + String iconResource = switch (quality) { + case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; + case 720 -> "revanced_video_quality_dialog_button_hd"; + case 1080 -> "revanced_video_quality_dialog_button_fhd"; + case 1440 -> "revanced_video_quality_dialog_button_2k"; + case 2160 -> "revanced_video_quality_dialog_button_4k"; + default -> "revanced_video_quality_dialog_button"; + }; + + instance.setIcon(iconResource); + Logger.printDebug(() -> "Updated button icon to: " + iconResource); + } catch (Exception ex) { + Logger.printException(() -> "Failed to update button icon", ex); + } + } + + /** + * Injection point. + */ + public static void initializeButton(View controlsView) { + try { + instance = new PlayerControlButton( + controlsView, + "revanced_video_quality_dialog_button", + "revanced_video_quality_dialog_button_placeholder", + Settings.VIDEO_QUALITY_DIALOG_BUTTON::get, + view -> { + try { + RememberVideoQualityPatch.showVideoQualityDialog(view.getContext()); + updateButtonIcon(); // Update icon after dialog interaction + } catch (Exception ex) { + Logger.printException(() -> "Video quality button onClick failure", ex); + } + }, + view -> { + try { + // Reset to automatic quality. + final int autoQuality = -2; // Auto. + RememberVideoQualityPatch.userChangedQualityInNewFlyout(autoQuality); + updateButtonIcon(); // Update icon after reset. + showToastShort(str("revanced_video_quality_reset_toast")); + return true; + } catch (Exception ex) { + Logger.printException(() -> "Video quality button reset failure", ex); + return false; + } + } + ); + updateButtonIcon(); // Set initial icon. + } catch (Exception ex) { + Logger.printException(() -> "initializeButton failure", ex); + } + } + + /** + * Injection point. + */ + public static void setVisibilityImmediate(boolean visible) { + if (instance != null) { + instance.setVisibilityImmediate(visible); + if (visible) updateButtonIcon(); + } + } + + /** + * Injection point. + */ + public static void setVisibility(boolean visible, boolean animated) { + if (instance != null) { + instance.setVisibility(visible, animated); + if (visible) updateButtonIcon(); + } + } +} + diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt index 2fb92c848c..432b4f8f39 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/VideoQualityPatch.kt @@ -5,6 +5,7 @@ import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.video.quality.button.videoQualityButtonPatch /** * Video quality settings. Used to organize all speed related settings together. @@ -19,6 +20,7 @@ val videoQualityPatch = bytecodePatch( dependsOn( rememberVideoQualityPatch, advancedVideoQualityMenuPatch, + videoQualityButtonPatch, ) compatibleWith( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt new file mode 100644 index 0000000000..1a9bc25904 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -0,0 +1,62 @@ +package app.revanced.patches.youtube.video.quality.button + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.playercontrols.* +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.patches.youtube.video.quality.rememberVideoQualityPatch +import app.revanced.util.ResourceGroup +import app.revanced.util.copyResources + +private val videoQualityButtonResourcePatch = resourcePatch { + dependsOn(playerControlsResourcePatch) + + execute { + copyResources( + "qualitybutton", + ResourceGroup( + "drawable", + "revanced_video_quality_dialog_button.xml", + "revanced_video_quality_dialog_button_4k.xml", + "revanced_video_quality_dialog_button_fhd.xml", + "revanced_video_quality_dialog_button_hd.xml", + "revanced_video_quality_dialog_button_lhd.xml", + "revanced_video_quality_dialog_button_qhd.xml", + ), + ) + + addBottomControl("qualitybutton") + } +} + +private const val QUALITY_BUTTON_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/videoplayer/VideoQualityDialogButton;" + +val videoQualityButtonPatch = bytecodePatch( + description = "Adds the option to display video quality dialog button in the video player.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + rememberVideoQualityPatch, + videoQualityButtonResourcePatch, + playerControlsPatch, + ) + + execute { + addResources("youtube", "video.quality.button.videoQualityButtonPatch") + + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("revanced_video_quality_dialog_button"), + ) + + initializeBottomControl(QUALITY_BUTTON_CLASS_DESCRIPTOR) + injectVisibilityCheckCall(QUALITY_BUTTON_CLASS_DESCRIPTOR) + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index f2e0a1ff3e..333ab5e55d 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1538,6 +1538,14 @@ Enabling this can unlock higher video qualities" Button is shown. Tap and hold to reset playback speed to default Button is not shown + + Show video quality button + Button is shown. Tap and hold to reset video quality to Auto + Button is not shown + No quality available + Changed video quality to: %s + Reset video quality to Auto + Custom playback speed menu Custom speed menu is shown diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml new file mode 100644 index 0000000000..f4be45538b --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_4k.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_4k.xml new file mode 100644 index 0000000000..22051e3fb8 --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_4k.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd.xml new file mode 100644 index 0000000000..b7a8f5c73e --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml new file mode 100644 index 0000000000..9b3fe6d097 --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml new file mode 100644 index 0000000000..400a95044d --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_qhd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_qhd.xml new file mode 100644 index 0000000000..7609d368ec --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_qhd.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml new file mode 100644 index 0000000000..e26adf5a65 --- /dev/null +++ b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -0,0 +1,29 @@ + + + + + + + From 5560caf17cf6856c1b6a80ebfcd8745d23623aea Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:35:37 +0300 Subject: [PATCH 02/57] refactor --- .../quality/RememberVideoQualityPatch.java | 355 +++++++++++++++++- .../videoplayer/VideoQualityDialogButton.java | 25 +- 2 files changed, 364 insertions(+), 16 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 59d76324e9..edf8731d8c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -2,13 +2,39 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.NetworkType; - +import static app.revanced.extension.shared.Utils.dipToPixels; + +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.LayoutInflater; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.ImageView; +import android.widget.ArrayAdapter; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.libraries.youtube.innertube.model.media.VideoQuality; import java.util.Arrays; import java.util.List; +import java.util.ArrayList; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; @@ -54,6 +80,12 @@ public interface VideoQualityMenuInterface { @Nullable private static List videoQualities; + /** + * The current VideoQualityMenuInterface, set during setVideoQuality. + */ + @Nullable + private static VideoQualityMenuInterface currentMenuInterface; + private static boolean shouldRememberVideoQuality() { BooleanSetting preference = ShortsPlayerState.isOpen() ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED @@ -94,6 +126,8 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte try { Utils.verifyOnMainThread(); + currentMenuInterface = menu; // Store the menu interface. + final boolean useShortsPreference = ShortsPlayerState.isOpen(); final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() @@ -209,5 +243,324 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; videoQualities = null; + currentMenuInterface = null; + } + + /** + * Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality. + */ + public static void showVideoQualityDialog(@NonNull Context context) { + try { + Dialog dialog = new Dialog(context); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setCanceledOnTouchOutside(true); + dialog.setCancelable(true); + + final int dip4 = dipToPixels(4); // Height for handle bar. + final int dip5 = dipToPixels(5); // Padding for mainLayout. + final int dip6 = dipToPixels(6); // Bottom margin. + final int dip8 = dipToPixels(8); // Side padding. + final int dip16 = dipToPixels(16); // Increased left padding for ListView. + final int dip20 = dipToPixels(20); // Margin below handle. + final int dip40 = dipToPixels(40); // Width for handle bar. + + LinearLayout mainLayout = new LinearLayout(context); + mainLayout.setOrientation(LinearLayout.VERTICAL); + mainLayout.setPadding(dip5, dip8, dip5, dip8); + + ShapeDrawable background = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(12), null, null)); + background.getPaint().setColor(Utils.getDialogBackgroundColor()); + mainLayout.setBackground(background); + + View handleBar = new View(context); + ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(4), null, null)); + handleBackground.getPaint().setColor(getAdjustedHandleBarBackgroundColor()); + handleBar.setBackground(handleBackground); + LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4); + handleParams.gravity = Gravity.CENTER_HORIZONTAL; + handleParams.setMargins(0, 0, 0, dip20); + handleBar.setLayoutParams(handleParams); + mainLayout.addView(handleBar); + + // Add title with current quality. + TextView titleView = new TextView(context); + boolean isShorts = ShortsPlayerState.isOpen(); + int currentQuality = Utils.getNetworkType() == NetworkType.MOBILE + ? (isShorts ? shortsQualityMobile : videoQualityMobile).get() + : (isShorts ? shortsQualityWifi : videoQualityWifi).get(); + String currentQualityLabel; + if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE || videoQualities == null) { + currentQualityLabel = str("video_quality_quick_menu_auto_toast"); + } else { + currentQualityLabel = str("video_quality_quick_menu_auto_toast"); + for (VideoQuality quality : videoQualities) { + if (quality.patch_getResolution() == currentQuality) { + currentQualityLabel = quality.patch_getQualityName(); + break; + } + } + } + + // Create SpannableStringBuilder for formatted text. + SpannableStringBuilder spannableTitle = new SpannableStringBuilder(); + String titlePart = str("video_quality_quick_menu_title"); + String separatorPart = str("video_quality_title_seperator"); + String qualityPart = currentQualityLabel; + + // Append title part with default foreground color. + spannableTitle.append(titlePart); + spannableTitle.setSpan( + new ForegroundColorSpan(Utils.getAppForegroundColor()), + 0, + titlePart.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + spannableTitle.append(" "); // Space after title. + + // Append separator part with adjusted handle bar color. + int separatorStart = spannableTitle.length(); + spannableTitle.append(separatorPart); + spannableTitle.setSpan( + new ForegroundColorSpan(getAdjustedTitleBarBackgroundColor()), + separatorStart, + separatorStart + separatorPart.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + spannableTitle.append(" "); // Space after separator. + + // Append quality label with adjusted handle bar color. + int qualityStart = spannableTitle.length(); + spannableTitle.append(qualityPart); + spannableTitle.setSpan( + new ForegroundColorSpan(getAdjustedTitleBarBackgroundColor()), + qualityStart, + qualityStart + qualityPart.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + titleView.setText(spannableTitle); + titleView.setTextSize(16); + // Remove setTextColor since color is handled by SpannableStringBuilder. + LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + titleParams.setMargins(dip8, 0, 0, dip20); + titleView.setLayoutParams(titleParams); + mainLayout.addView(titleView); + + List qualityLabels = new ArrayList<>(); + List qualityIndices = new ArrayList<>(); + if (videoQualities != null) { + for (int i = 0; i < videoQualities.size(); i++) { + VideoQuality quality = videoQualities.get(i); + int resolution = quality.patch_getResolution(); + if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE) { + qualityLabels.add(quality.patch_getQualityName()); + qualityIndices.add(i); + } + } + } + + int selectedIndex = -1; + if (videoQualities != null && currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { + for (int i = 0; i < videoQualities.size(); i++) { + if (videoQualities.get(i).patch_getResolution() == currentQuality) { + selectedIndex = qualityIndices.indexOf(i); + break; + } + } + } + if (selectedIndex < 0 && !qualityLabels.isEmpty()) { + selectedIndex = 0; + } + + ListView listView = new ListView(context); + CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); + adapter.setSelectedPosition(selectedIndex); + listView.setAdapter(adapter); + listView.setDivider(null); + listView.setPadding(dip16, 0, 0, 0); + + listView.setOnItemClickListener((parent, view, which, id) -> { + try { + if (currentMenuInterface == null) { + Logger.printDebug(() -> "VideoQualityMenuInterface is null in showVideoQualityDialog"); + return; + } + int originalIndex = qualityIndices.get(which); + VideoQuality selectedQuality = videoQualities.get(originalIndex); + currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); + Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality.patch_getQualityName() + " (index: " + originalIndex + ")"); + + if (shouldRememberVideoQuality()) { + changeDefaultQuality(selectedQuality.patch_getResolution()); + } + + Utils.showToastShort(str("revanced_video_quality_selected_toast", qualityLabels.get(which))); + dialog.dismiss(); + } catch (Exception ex) { + Logger.printException(() -> "Video quality selection failure", ex); + } + }); + + LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + listViewParams.setMargins(0, 0, 0, dip5); + listView.setLayoutParams(listViewParams); + mainLayout.addView(listView); + + LinearLayout wrapperLayout = new LinearLayout(context); + wrapperLayout.setOrientation(LinearLayout.VERTICAL); + wrapperLayout.setPadding(dip8, 0, dip8, 0); + wrapperLayout.addView(mainLayout); + dialog.setContentView(wrapperLayout); + + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = Gravity.BOTTOM; + params.y = dip6; + int portraitWidth = context.getResources().getDisplayMetrics().widthPixels; + if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + portraitWidth = Math.min( + portraitWidth, + context.getResources().getDisplayMetrics().heightPixels); + } + params.width = portraitWidth; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(params); + window.setBackgroundDrawable(null); + } + + final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast"); + Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom"); + slideInABottomAnimation.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideInABottomAnimation); + + mainLayout.setOnTouchListener(new View.OnTouchListener() { + final float dismissThreshold = dipToPixels(100); + float touchY; + float translationY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchY = event.getRawY(); + translationY = mainLayout.getTranslationY(); + return true; + case MotionEvent.ACTION_MOVE: + final float deltaY = event.getRawY() - touchY; + if (deltaY >= 0) { + mainLayout.setTranslationY(translationY + deltaY); + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mainLayout.getTranslationY() > dismissThreshold) { + final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels + - mainLayout.getTop(); + TranslateAnimation slideOut = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), remainingDistance); + slideOut.setDuration(fadeDurationFast); + slideOut.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + @Override + public void onAnimationEnd(Animation animation) { + dialog.dismiss(); + } + @Override + public void onAnimationRepeat(Animation animation) {} + }); + mainLayout.startAnimation(slideOut); + } else { + TranslateAnimation slideBack = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), 0); + slideBack.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideBack); + mainLayout.setTranslationY(0); + } + return true; + default: + return false; + } + } + }); + + dialog.show(); + } catch (Exception ex) { + Logger.printException(() -> "showVideoQualityDialog failure", ex); + } + } + + public static class CustomQualityAdapter extends ArrayAdapter { + private int selectedPosition = -1; + + public CustomQualityAdapter(@NonNull Context context, @NonNull List objects) { + super(context, 0, objects); + } + + public void setSelectedPosition(int position) { + this.selectedPosition = position; + notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate( + Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"), + parent, + false + ); + viewHolder = new ViewHolder(); + viewHolder.checkIcon = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon", "id") + ); + viewHolder.placeholder = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon_placeholder", "id") + ); + viewHolder.textView = convertView.findViewById( + Utils.getResourceIdentifier("revanced_item_text", "id") + ); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + + viewHolder.textView.setText(getItem(position)); + boolean isSelected = position == selectedPosition; + viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); + viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE); + + return convertView; + } + + private static class ViewHolder { + ImageView checkIcon; + View placeholder; + TextView textView; + } + } + + public static int getAdjustedHandleBarBackgroundColor() { + final int baseColor = Utils.getDialogBackgroundColor(); + return Utils.isDarkModeEnabled() + ? Utils.adjustColorBrightness(baseColor, 1.25f) + : Utils.adjustColorBrightness(baseColor, 0.9f); + } + + public static int getAdjustedTitleBarBackgroundColor() { + final int baseColor = Utils.getAppForegroundColor(); + return Utils.isDarkModeEnabled() + ? Utils.adjustColorBrightness(baseColor, 0.6f) + : Utils.adjustColorBrightness(baseColor, 1.6f); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 13d49f5804..fb2592e5ce 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -5,10 +5,10 @@ import androidx.annotation.Nullable; import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch; import app.revanced.extension.youtube.settings.Settings; - -import java.util.List; +import app.revanced.extension.youtube.shared.ShortsPlayerState; import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.showToastShort; @@ -25,18 +25,14 @@ public static void updateButtonIcon() { if (instance == null) return; try { - // Get the current quality. - int lastAppliedQualityIndex = RememberVideoQualityPatch.getLastAppliedQualityIndex(); - List videoQualities = RememberVideoQualityPatch.getVideoQualities(); - - if (videoQualities == null || lastAppliedQualityIndex < 0 || lastAppliedQualityIndex >= videoQualities.size()) { - // Default to a generic icon or "Auto". - instance.setIcon("revanced_video_quality_dialog_button"); - return; - } + // Get the current preferred quality from settings based on network and player state. + boolean isShorts = ShortsPlayerState.isOpen(); + int currentQuality = Utils.getNetworkType() == Utils.NetworkType.MOBILE + ? (isShorts ? Settings.SHORTS_QUALITY_DEFAULT_MOBILE : Settings.VIDEO_QUALITY_DEFAULT_MOBILE).get() + : (isShorts ? Settings.SHORTS_QUALITY_DEFAULT_WIFI : Settings.VIDEO_QUALITY_DEFAULT_WIFI).get(); - int quality = videoQualities.get(lastAppliedQualityIndex); - String iconResource = switch (quality) { + // Map quality to appropriate icon. + String iconResource = switch (currentQuality) { case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; case 720 -> "revanced_video_quality_dialog_button_hd"; case 1080 -> "revanced_video_quality_dialog_button_fhd"; @@ -74,7 +70,7 @@ public static void initializeButton(View controlsView) { try { // Reset to automatic quality. final int autoQuality = -2; // Auto. - RememberVideoQualityPatch.userChangedQualityInNewFlyout(autoQuality); + RememberVideoQualityPatch.userChangedQualityInFlyout(autoQuality); updateButtonIcon(); // Update icon after reset. showToastShort(str("revanced_video_quality_reset_toast")); return true; @@ -110,4 +106,3 @@ public static void setVisibility(boolean visible, boolean animated) { } } } - From cf7e264890f1e380791a2acebfe4ec66446ffb18 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:24:45 +0300 Subject: [PATCH 03/57] fix empty quality list when Auto is selected --- .../quality/RememberVideoQualityPatch.java | 17 +++++++++++------ .../videoplayer/PlayerControlButton.java | 2 +- .../revanced_video_quality_dialog_button_hd.xml | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index edf8731d8c..0416a7161a 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -134,6 +134,11 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { + // Initialize videoQualities to ensure showVideoQualityDialog has data. + if (videoQualities == null || videoQualities.size() != qualities.length) { + videoQualities = Arrays.asList(qualities); + Logger.printDebug(() -> "VideoQualities initialized for Auto: " + videoQualities); + } return originalQualityIndex; // Nothing to do. } @@ -260,7 +265,7 @@ public static void showVideoQualityDialog(@NonNull Context context) { final int dip5 = dipToPixels(5); // Padding for mainLayout. final int dip6 = dipToPixels(6); // Bottom margin. final int dip8 = dipToPixels(8); // Side padding. - final int dip16 = dipToPixels(16); // Increased left padding for ListView. + final int dip16 = dipToPixels(16); // Left padding for ListView. final int dip20 = dipToPixels(20); // Margin below handle. final int dip40 = dipToPixels(40); // Width for handle bar. @@ -319,22 +324,22 @@ public static void showVideoQualityDialog(@NonNull Context context) { ); spannableTitle.append(" "); // Space after title. - // Append separator part with adjusted handle bar color. + // Append separator part with adjusted title color. int separatorStart = spannableTitle.length(); spannableTitle.append(separatorPart); spannableTitle.setSpan( - new ForegroundColorSpan(getAdjustedTitleBarBackgroundColor()), + new ForegroundColorSpan(getAdjustedTitleForegroundColor()), separatorStart, separatorStart + separatorPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); spannableTitle.append(" "); // Space after separator. - // Append quality label with adjusted handle bar color. + // Append quality label with adjusted title color. int qualityStart = spannableTitle.length(); spannableTitle.append(qualityPart); spannableTitle.setSpan( - new ForegroundColorSpan(getAdjustedTitleBarBackgroundColor()), + new ForegroundColorSpan(getAdjustedTitleForegroundColor()), qualityStart, qualityStart + qualityPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE @@ -557,7 +562,7 @@ public static int getAdjustedHandleBarBackgroundColor() { : Utils.adjustColorBrightness(baseColor, 0.9f); } - public static int getAdjustedTitleBarBackgroundColor() { + public static int getAdjustedTitleForegroundColor() { final int baseColor = Utils.getAppForegroundColor(); return Utils.isDarkModeEnabled() ? Utils.adjustColorBrightness(baseColor, 0.6f) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index 5d3ae3a543..77b69825f0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -208,4 +208,4 @@ public void setIcon(String resourceName) { Logger.printException(() -> "setIcon failure", ex); } } -} \ No newline at end of file +} diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml index 9b3fe6d097..db2bad1b1f 100644 --- a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_hd.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + From e7ee115e7c9e8cd8098c363024f0d83cdcea8256 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 06:15:09 -0400 Subject: [PATCH 04/57] apidump --- patches/api/patches.api | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/patches/api/patches.api b/patches/api/patches.api index 559de899f8..91f78e8db7 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1664,6 +1664,10 @@ public final class app/revanced/patches/youtube/video/quality/VideoQualityPatchK public static final fun getVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatchKt { + public static final fun getVideoQualityButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/video/speed/PlaybackSpeedPatchKt { public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } From a09977e13416017e1a578e69dfdd066418842654 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:37:57 +0300 Subject: [PATCH 05/57] fix reset to Auto --- .../quality/RememberVideoQualityPatch.java | 48 +++++++++++++------ .../videoplayer/VideoQualityDialogButton.java | 9 +++- .../resources/addresources/values/strings.xml | 2 - 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 0416a7161a..6c217e8952 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -106,14 +106,18 @@ private static void changeDefaultQuality(int qualityResolution) { } qualitySetting.save(qualityResolution); - if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) + if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { + String qualityLabel = qualityResolution == AUTOMATIC_VIDEO_QUALITY_VALUE + ? str("video_quality_quick_menu_auto_toast") + : qualityResolution + "p"; Utils.showToastShort(str( shortPlayerOpen ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast", networkTypeMessage, - (qualityResolution + "p")) + qualityLabel) ); + } } /** @@ -357,19 +361,24 @@ public static void showVideoQualityDialog(@NonNull Context context) { List qualityLabels = new ArrayList<>(); List qualityIndices = new ArrayList<>(); - if (videoQualities != null) { - for (int i = 0; i < videoQualities.size(); i++) { - VideoQuality quality = videoQualities.get(i); - int resolution = quality.patch_getResolution(); - if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE) { - qualityLabels.add(quality.patch_getQualityName()); - qualityIndices.add(i); - } + for (int i = 0; i < videoQualities.size(); i++) { + VideoQuality quality = videoQualities.get(i); + int resolution = quality.patch_getResolution(); + if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE) { + qualityLabels.add(quality.patch_getQualityName()); + qualityIndices.add(i); } } + if (qualityLabels.isEmpty()) { + Logger.printDebug(() -> "showVideoQualityDialog: No valid video qualities available"); + dialog.dismiss(); + return; + } + + // Only select an index if currentQuality is not Auto. int selectedIndex = -1; - if (videoQualities != null && currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { + if (currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { for (int i = 0; i < videoQualities.size(); i++) { if (videoQualities.get(i).patch_getResolution() == currentQuality) { selectedIndex = qualityIndices.indexOf(i); @@ -377,9 +386,6 @@ public static void showVideoQualityDialog(@NonNull Context context) { } } } - if (selectedIndex < 0 && !qualityLabels.isEmpty()) { - selectedIndex = 0; - } ListView listView = new ListView(context); CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); @@ -403,7 +409,9 @@ public static void showVideoQualityDialog(@NonNull Context context) { changeDefaultQuality(selectedQuality.patch_getResolution()); } - Utils.showToastShort(str("revanced_video_quality_selected_toast", qualityLabels.get(which))); + if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { + Utils.showToastShort(str("revanced_video_quality_selected_toast", qualityLabels.get(which))); + } dialog.dismiss(); } catch (Exception ex) { Logger.printException(() -> "Video quality selection failure", ex); @@ -568,4 +576,14 @@ public static int getAdjustedTitleForegroundColor() { ? Utils.adjustColorBrightness(baseColor, 0.6f) : Utils.adjustColorBrightness(baseColor, 1.6f); } + + @Nullable + public static VideoQualityMenuInterface getCurrentMenuInterface() { + return currentMenuInterface; + } + + @Nullable + public static List getVideoQualities() { + return videoQualities; + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index fb2592e5ce..8506aebe44 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -61,7 +61,7 @@ public static void initializeButton(View controlsView) { view -> { try { RememberVideoQualityPatch.showVideoQualityDialog(view.getContext()); - updateButtonIcon(); // Update icon after dialog interaction + updateButtonIcon(); // Update icon after dialog interaction. } catch (Exception ex) { Logger.printException(() -> "Video quality button onClick failure", ex); } @@ -71,8 +71,13 @@ public static void initializeButton(View controlsView) { // Reset to automatic quality. final int autoQuality = -2; // Auto. RememberVideoQualityPatch.userChangedQualityInFlyout(autoQuality); + // Apply automatic quality immediately. + if (RememberVideoQualityPatch.getCurrentMenuInterface() != null && RememberVideoQualityPatch.getVideoQualities() != null) { + RememberVideoQualityPatch.getCurrentMenuInterface().patch_setMenuIndexFromQuality( + RememberVideoQualityPatch.getVideoQualities().get(0)); // Auto is index 0. + Logger.printDebug(() -> "Applied automatic quality via long press"); + } updateButtonIcon(); // Update icon after reset. - showToastShort(str("revanced_video_quality_reset_toast")); return true; } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 2dd079e7fe..be666b0b47 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1547,9 +1547,7 @@ Enabling this can unlock higher video qualities" Show video quality button Button is shown. Tap and hold to reset video quality to Auto Button is not shown - No quality available Changed video quality to: %s - Reset video quality to Auto Custom playback speed menu From d94040cb3a02a0f71b5c83b12c4f81a7a69fd26b Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:05:03 -0400 Subject: [PATCH 06/57] refactor --- .../quality/RememberVideoQualityPatch.java | 21 +++++---- .../videoplayer/PlayerControlButton.java | 12 ++--- .../videoplayer/VideoQualityDialogButton.java | 47 +++++++++++-------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 6c217e8952..f2e12fc82d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -93,6 +93,13 @@ private static boolean shouldRememberVideoQuality() { return preference.get(); } + public static int getDefaultVideoQuality() { + final boolean isShorts = ShortsPlayerState.isOpen(); + return Utils.getNetworkType() == NetworkType.MOBILE + ? (isShorts ? shortsQualityMobile : videoQualityMobile).get() + : (isShorts ? shortsQualityWifi : videoQualityWifi).get(); + } + private static void changeDefaultQuality(int qualityResolution) { final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; @@ -184,13 +191,12 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte // If the desired quality index is equal to the original index, // then the video is already set to the desired default quality. - String qualityToUseName = qualityToUse.patch_getQualityName(); + VideoQuality qualityToUseFinal = qualityToUse; if (qualityIndexToUse == originalQualityIndex) { - Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseName); + Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal); } else { Logger.printDebug(() -> "Changing video quality from: " - + videoQualities.get(originalQualityIndex).patch_getQualityName() - + " to: " + qualityToUseName); + + videoQualities.get(originalQualityIndex) + " to: " + qualityToUseFinal); } // On first load of a new video, if the video is already the desired quality @@ -258,7 +264,7 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl /** * Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality. */ - public static void showVideoQualityDialog(@NonNull Context context) { + public static void showVideoQualityDialog(Context context) { try { Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -295,10 +301,7 @@ public static void showVideoQualityDialog(@NonNull Context context) { // Add title with current quality. TextView titleView = new TextView(context); - boolean isShorts = ShortsPlayerState.isOpen(); - int currentQuality = Utils.getNetworkType() == NetworkType.MOBILE - ? (isShorts ? shortsQualityMobile : videoQualityMobile).get() - : (isShorts ? shortsQualityWifi : videoQualityWifi).get(); + final int currentQuality = getDefaultVideoQuality(); String currentQualityLabel; if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE || videoQualities == null) { currentQualityLabel = str("video_quality_quick_menu_auto_toast"); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index 77b69825f0..e2ce909970 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -195,14 +195,10 @@ public void hide() { public void setIcon(String resourceName) { try { View button = buttonRef.get(); - if (button instanceof ImageView) { - int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); - if (resourceId != 0) { - ((ImageView) button).setImageResource(resourceId); - Logger.printDebug(() -> "Set button icon to: " + resourceName); - } else { - Logger.printDebug(() -> "Resource not found: " + resourceName); - } + if (button instanceof ImageView imageButton) { + final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); + imageButton.setImageResource(resourceId); + Logger.printDebug(() -> "Set button icon to: " + resourceName); } } catch (Exception ex) { Logger.printException(() -> "setIcon failure", ex); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 8506aebe44..66d03dbadc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -4,32 +4,34 @@ import androidx.annotation.Nullable; +import com.google.android.libraries.youtube.innertube.model.media.VideoQuality; + +import java.util.List; + import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.youtube.shared.ShortsPlayerState; - -import static app.revanced.extension.shared.StringRef.str; -import static app.revanced.extension.shared.Utils.showToastShort; @SuppressWarnings("unused") public class VideoQualityDialogButton { @Nullable private static PlayerControlButton instance; + /** + * The current resource name of the button icon. + */ + @Nullable + private static String currentIconResource; + /** * Updates the button icon based on the current video quality. */ public static void updateButtonIcon() { - if (instance == null) return; - try { - // Get the current preferred quality from settings based on network and player state. - boolean isShorts = ShortsPlayerState.isOpen(); - int currentQuality = Utils.getNetworkType() == Utils.NetworkType.MOBILE - ? (isShorts ? Settings.SHORTS_QUALITY_DEFAULT_MOBILE : Settings.VIDEO_QUALITY_DEFAULT_MOBILE).get() - : (isShorts ? Settings.SHORTS_QUALITY_DEFAULT_WIFI : Settings.VIDEO_QUALITY_DEFAULT_WIFI).get(); + if (instance == null) return; + + //noinspection ExtractMethodRecommender + final int currentQuality = RememberVideoQualityPatch.getDefaultVideoQuality(); // Map quality to appropriate icon. String iconResource = switch (currentQuality) { @@ -41,8 +43,11 @@ public static void updateButtonIcon() { default -> "revanced_video_quality_dialog_button"; }; - instance.setIcon(iconResource); - Logger.printDebug(() -> "Updated button icon to: " + iconResource); + if (!iconResource.equals(currentIconResource)) { + currentIconResource = iconResource; + instance.setIcon(iconResource); + Logger.printDebug(() -> "Updated button icon to: " + iconResource); + } } catch (Exception ex) { Logger.printException(() -> "Failed to update button icon", ex); } @@ -69,14 +74,17 @@ public static void initializeButton(View controlsView) { view -> { try { // Reset to automatic quality. - final int autoQuality = -2; // Auto. - RememberVideoQualityPatch.userChangedQualityInFlyout(autoQuality); + RememberVideoQualityPatch.userChangedQualityInFlyout( + RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE); // Apply automatic quality immediately. - if (RememberVideoQualityPatch.getCurrentMenuInterface() != null && RememberVideoQualityPatch.getVideoQualities() != null) { - RememberVideoQualityPatch.getCurrentMenuInterface().patch_setMenuIndexFromQuality( - RememberVideoQualityPatch.getVideoQualities().get(0)); // Auto is index 0. + RememberVideoQualityPatch.VideoQualityMenuInterface menu + = RememberVideoQualityPatch.getCurrentMenuInterface(); + List qualities = RememberVideoQualityPatch.getVideoQualities(); + if (menu != null && qualities != null) { + menu.patch_setMenuIndexFromQuality(qualities.get(0)); // Auto is index 0. Logger.printDebug(() -> "Applied automatic quality via long press"); } + updateButtonIcon(); // Update icon after reset. return true; } catch (Exception ex) { @@ -85,6 +93,7 @@ public static void initializeButton(View controlsView) { } } ); + updateButtonIcon(); // Set initial icon. } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); From 2c2d21ce649955c96feee9f8fdf125754d060d27 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:06:03 -0400 Subject: [PATCH 07/57] Fix error if quality button is used before the video loads --- .../patches/playback/quality/RememberVideoQualityPatch.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index f2e12fc82d..6cb9336c1f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -266,6 +266,11 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl */ public static void showVideoQualityDialog(Context context) { try { + if (videoQualities == null) { + Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); + return; + } + Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setCanceledOnTouchOutside(true); From 6b0c01a5df6d73575baa7879e889bd2ea0301f82 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:17:27 -0400 Subject: [PATCH 08/57] refactor --- .../quality/RememberVideoQualityPatch.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 6cb9336c1f..d6eb7c2db6 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -54,7 +54,7 @@ public interface VideoQualityMenuInterface { void patch_setMenuIndexFromQuality(VideoQuality quality); } - private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; + public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI; @@ -266,7 +266,8 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl */ public static void showVideoQualityDialog(Context context) { try { - if (videoQualities == null) { + List qualities = videoQualities; + if (qualities == null) { Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); return; } @@ -308,7 +309,7 @@ public static void showVideoQualityDialog(Context context) { TextView titleView = new TextView(context); final int currentQuality = getDefaultVideoQuality(); String currentQualityLabel; - if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE || videoQualities == null) { + if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { currentQualityLabel = str("video_quality_quick_menu_auto_toast"); } else { currentQualityLabel = str("video_quality_quick_menu_auto_toast"); @@ -367,12 +368,12 @@ public static void showVideoQualityDialog(Context context) { titleView.setLayoutParams(titleParams); mainLayout.addView(titleView); - List qualityLabels = new ArrayList<>(); - List qualityIndices = new ArrayList<>(); - for (int i = 0; i < videoQualities.size(); i++) { - VideoQuality quality = videoQualities.get(i); - int resolution = quality.patch_getResolution(); - if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE) { + final int qualitySize = qualities.size(); + List qualityLabels = new ArrayList<>(qualitySize); + List qualityIndices = new ArrayList<>(qualitySize); + for (int i = 0; i < qualitySize; i++) { + VideoQuality quality = qualities.get(i); + if (quality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { qualityLabels.add(quality.patch_getQualityName()); qualityIndices.add(i); } @@ -387,8 +388,8 @@ public static void showVideoQualityDialog(Context context) { // Only select an index if currentQuality is not Auto. int selectedIndex = -1; if (currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { - for (int i = 0; i < videoQualities.size(); i++) { - if (videoQualities.get(i).patch_getResolution() == currentQuality) { + for (int i = 0; i < qualitySize; i++) { + if (qualities.get(i).patch_getResolution() == currentQuality) { selectedIndex = qualityIndices.indexOf(i); break; } @@ -406,12 +407,13 @@ public static void showVideoQualityDialog(Context context) { try { if (currentMenuInterface == null) { Logger.printDebug(() -> "VideoQualityMenuInterface is null in showVideoQualityDialog"); + dialog.dismiss(); return; } - int originalIndex = qualityIndices.get(which); + final int originalIndex = qualityIndices.get(which); VideoQuality selectedQuality = videoQualities.get(originalIndex); currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); - Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality.patch_getQualityName() + " (index: " + originalIndex + ")"); + Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " index: " + originalIndex); if (shouldRememberVideoQuality()) { changeDefaultQuality(selectedQuality.patch_getResolution()); @@ -461,6 +463,7 @@ public static void showVideoQualityDialog(Context context) { slideInABottomAnimation.setDuration(fadeDurationFast); mainLayout.startAnimation(slideInABottomAnimation); + // noinspection ClickableViewAccessibility mainLayout.setOnTouchListener(new View.OnTouchListener() { final float dismissThreshold = dipToPixels(100); float touchY; @@ -482,6 +485,7 @@ public boolean onTouch(View v, MotionEvent event) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mainLayout.getTranslationY() > dismissThreshold) { + //noinspection ExtractMethodRecommender final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels - mainLayout.getTop(); TranslateAnimation slideOut = new TranslateAnimation( @@ -557,7 +561,7 @@ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup } viewHolder.textView.setText(getItem(position)); - boolean isSelected = position == selectedPosition; + final boolean isSelected = position == selectedPosition; viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE); From c067db464c23a59f61b180c853b8605f9fef1894 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:25:12 -0400 Subject: [PATCH 09/57] refactor --- .../quality/RememberVideoQualityPatch.java | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index d6eb7c2db6..d79101f04b 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -137,31 +137,23 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte try { Utils.verifyOnMainThread(); - currentMenuInterface = menu; // Store the menu interface. + currentMenuInterface = menu; // Later used by player quality button. final boolean useShortsPreference = ShortsPlayerState.isOpen(); final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); - if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { - // Initialize videoQualities to ensure showVideoQualityDialog has data. - if (videoQualities == null || videoQualities.size() != qualities.length) { - videoQualities = Arrays.asList(qualities); - Logger.printDebug(() -> "VideoQualities initialized for Auto: " + videoQualities); - } - return originalQualityIndex; // Nothing to do. - } - - if (videoQualities == null || videoQualities.size() != qualities.length) { + final boolean qualitiesChanged = videoQualities == null || videoQualities.size() != qualities.length; + if (qualitiesChanged) { videoQualities = Arrays.asList(qualities); - - // After changing videos the qualities can initially be for the prior video. - // So if the qualities have changed an update is needed. - qualityNeedsUpdating = true; Logger.printDebug(() -> "VideoQualities: " + videoQualities); } + if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { + return originalQualityIndex; // Nothing to do. + } + if (userChangedDefaultQuality) { userChangedDefaultQuality = false; VideoQuality quality = videoQualities.get(userSelectedQualityIndex); @@ -170,7 +162,9 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte return userSelectedQualityIndex; } - if (!qualityNeedsUpdating) { + // After changing videos the qualities can initially be for the prior video. + // If the qualities have changed and the default is not auto then an update is needed. + if (!qualityNeedsUpdating && !qualitiesChanged) { return originalQualityIndex; } qualityNeedsUpdating = false; @@ -308,16 +302,20 @@ public static void showVideoQualityDialog(Context context) { // Add title with current quality. TextView titleView = new TextView(context); final int currentQuality = getDefaultVideoQuality(); + int listViewSelectedIndex = -1; String currentQualityLabel; if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { currentQualityLabel = str("video_quality_quick_menu_auto_toast"); } else { currentQualityLabel = str("video_quality_quick_menu_auto_toast"); - for (VideoQuality quality : videoQualities) { + int i = 0; + for (VideoQuality quality : qualities) { if (quality.patch_getResolution() == currentQuality) { currentQualityLabel = quality.patch_getQualityName(); + listViewSelectedIndex = i - 1; // Adjust for automatic quality at first index. break; } + i++; } } @@ -370,12 +368,10 @@ public static void showVideoQualityDialog(Context context) { final int qualitySize = qualities.size(); List qualityLabels = new ArrayList<>(qualitySize); - List qualityIndices = new ArrayList<>(qualitySize); for (int i = 0; i < qualitySize; i++) { VideoQuality quality = qualities.get(i); if (quality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { qualityLabels.add(quality.patch_getQualityName()); - qualityIndices.add(i); } } @@ -385,20 +381,9 @@ public static void showVideoQualityDialog(Context context) { return; } - // Only select an index if currentQuality is not Auto. - int selectedIndex = -1; - if (currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { - for (int i = 0; i < qualitySize; i++) { - if (qualities.get(i).patch_getResolution() == currentQuality) { - selectedIndex = qualityIndices.indexOf(i); - break; - } - } - } - ListView listView = new ListView(context); CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); - adapter.setSelectedPosition(selectedIndex); + adapter.setSelectedPosition(listViewSelectedIndex); listView.setAdapter(adapter); listView.setDivider(null); listView.setPadding(dip16, 0, 0, 0); @@ -410,7 +395,7 @@ public static void showVideoQualityDialog(Context context) { dialog.dismiss(); return; } - final int originalIndex = qualityIndices.get(which); + final int originalIndex = which + 1; VideoQuality selectedQuality = videoQualities.get(originalIndex); currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " index: " + originalIndex); From 0b020c1f65b10887e8a919793bf6dbc8b1477fc5 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:27:55 -0400 Subject: [PATCH 10/57] Remove unneeded toast --- .../patches/playback/quality/RememberVideoQualityPatch.java | 5 +---- patches/src/main/resources/addresources/values/strings.xml | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index d79101f04b..fa3dcc8f4e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -396,7 +396,7 @@ public static void showVideoQualityDialog(Context context) { return; } final int originalIndex = which + 1; - VideoQuality selectedQuality = videoQualities.get(originalIndex); + VideoQuality selectedQuality = qualities.get(originalIndex); currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " index: " + originalIndex); @@ -404,9 +404,6 @@ public static void showVideoQualityDialog(Context context) { changeDefaultQuality(selectedQuality.patch_getResolution()); } - if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { - Utils.showToastShort(str("revanced_video_quality_selected_toast", qualityLabels.get(which))); - } dialog.dismiss(); } catch (Exception ex) { Logger.printException(() -> "Video quality selection failure", ex); diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index be666b0b47..80baa19af9 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1547,7 +1547,6 @@ Enabling this can unlock higher video qualities" Show video quality button Button is shown. Tap and hold to reset video quality to Auto Button is not shown - Changed video quality to: %s Custom playback speed menu From e9198a65ae307c3063413c5b09914828038074c9 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:39:17 -0400 Subject: [PATCH 11/57] Show error if resource is not found --- .../quality/RememberVideoQualityPatch.java | 32 +++++++++---------- .../videoplayer/PlayerControlButton.java | 4 +++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index fa3dcc8f4e..bfdb5de230 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -100,6 +100,16 @@ public static int getDefaultVideoQuality() { : (isShorts ? shortsQualityWifi : videoQualityWifi).get(); } + @Nullable + public static VideoQualityMenuInterface getCurrentMenuInterface() { + return currentMenuInterface; + } + + @Nullable + public static List getVideoQualities() { + return videoQualities; + } + private static void changeDefaultQuality(int qualityResolution) { final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; @@ -265,6 +275,11 @@ public static void showVideoQualityDialog(Context context) { Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); return; } + VideoQualityMenuInterface menu = currentMenuInterface; + if (menu == null) { + Logger.printDebug(() -> "Cannot show qualities dialog, menu is null"); + return; + } Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -390,14 +405,9 @@ public static void showVideoQualityDialog(Context context) { listView.setOnItemClickListener((parent, view, which, id) -> { try { - if (currentMenuInterface == null) { - Logger.printDebug(() -> "VideoQualityMenuInterface is null in showVideoQualityDialog"); - dialog.dismiss(); - return; - } final int originalIndex = which + 1; VideoQuality selectedQuality = qualities.get(originalIndex); - currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); + menu.patch_setMenuIndexFromQuality(selectedQuality); Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " index: " + originalIndex); if (shouldRememberVideoQuality()) { @@ -570,14 +580,4 @@ public static int getAdjustedTitleForegroundColor() { ? Utils.adjustColorBrightness(baseColor, 0.6f) : Utils.adjustColorBrightness(baseColor, 1.6f); } - - @Nullable - public static VideoQualityMenuInterface getCurrentMenuInterface() { - return currentMenuInterface; - } - - @Nullable - public static List getVideoQualities() { - return videoQualities; - } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index e2ce909970..f9eb6f9c02 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -197,6 +197,10 @@ public void setIcon(String resourceName) { View button = buttonRef.get(); if (button instanceof ImageView imageButton) { final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); + if (resourceId == 0) { + Logger.printException(() -> "Could not set button icon to: " + resourceName); + return; + } imageButton.setImageResource(resourceId); Logger.printDebug(() -> "Set button icon to: " + resourceName); } From f7e3d32dc1c9ffbe854aa1732823a7decb0174b0 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:43:17 +0300 Subject: [PATCH 12/57] fix 2k resource --- .../extension/youtube/videoplayer/VideoQualityDialogButton.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 66d03dbadc..8d0590fca4 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -38,7 +38,7 @@ public static void updateButtonIcon() { case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; case 720 -> "revanced_video_quality_dialog_button_hd"; case 1080 -> "revanced_video_quality_dialog_button_fhd"; - case 1440 -> "revanced_video_quality_dialog_button_2k"; + case 1440 -> "revanced_video_quality_dialog_button_qhd"; case 2160 -> "revanced_video_quality_dialog_button_4k"; default -> "revanced_video_quality_dialog_button"; }; From 313d7d9a5f8b0981c0e31cd80cd2be3ed105f6bc Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:46:10 -0400 Subject: [PATCH 13/57] refactor --- .../quality/RememberVideoQualityPatch.java | 73 +++++++++---------- .../videoplayer/VideoQualityDialogButton.java | 6 +- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index bfdb5de230..a926bbb0f9 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -281,6 +281,35 @@ public static void showVideoQualityDialog(Context context) { return; } + String currentQualityLabel = str("video_quality_quick_menu_auto_toast"); + final int currentQuality = getDefaultVideoQuality(); + int listViewSelectedIndex = -1; + if (currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { + int i = 0; + for (VideoQuality quality : qualities) { + if (quality.patch_getResolution() == currentQuality) { + currentQualityLabel = quality.patch_getQualityName(); + listViewSelectedIndex = i - 1; // Adjust for automatic quality at first index. + break; + } + i++; + } + } + + final int qualitySize = qualities.size(); + List qualityLabels = new ArrayList<>(qualitySize); + for (int i = 0; i < qualitySize; i++) { + VideoQuality quality = qualities.get(i); + if (quality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { + qualityLabels.add(quality.patch_getQualityName()); + } + } + + if (qualityLabels.isEmpty()) { + Logger.printException(() -> "Video has no available qualities"); + return; + } + Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setCanceledOnTouchOutside(true); @@ -314,31 +343,10 @@ public static void showVideoQualityDialog(Context context) { handleBar.setLayoutParams(handleParams); mainLayout.addView(handleBar); - // Add title with current quality. - TextView titleView = new TextView(context); - final int currentQuality = getDefaultVideoQuality(); - int listViewSelectedIndex = -1; - String currentQualityLabel; - if (currentQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { - currentQualityLabel = str("video_quality_quick_menu_auto_toast"); - } else { - currentQualityLabel = str("video_quality_quick_menu_auto_toast"); - int i = 0; - for (VideoQuality quality : qualities) { - if (quality.patch_getResolution() == currentQuality) { - currentQualityLabel = quality.patch_getQualityName(); - listViewSelectedIndex = i - 1; // Adjust for automatic quality at first index. - break; - } - i++; - } - } - // Create SpannableStringBuilder for formatted text. SpannableStringBuilder spannableTitle = new SpannableStringBuilder(); String titlePart = str("video_quality_quick_menu_title"); String separatorPart = str("video_quality_title_seperator"); - String qualityPart = currentQualityLabel; // Append title part with default foreground color. spannableTitle.append(titlePart); @@ -362,15 +370,17 @@ public static void showVideoQualityDialog(Context context) { spannableTitle.append(" "); // Space after separator. // Append quality label with adjusted title color. - int qualityStart = spannableTitle.length(); - spannableTitle.append(qualityPart); + final int qualityStart = spannableTitle.length(); + spannableTitle.append(currentQualityLabel); spannableTitle.setSpan( new ForegroundColorSpan(getAdjustedTitleForegroundColor()), qualityStart, - qualityStart + qualityPart.length(), + qualityStart + currentQualityLabel.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); + // Add title with current quality. + TextView titleView = new TextView(context); titleView.setText(spannableTitle); titleView.setTextSize(16); // Remove setTextColor since color is handled by SpannableStringBuilder. @@ -381,21 +391,6 @@ public static void showVideoQualityDialog(Context context) { titleView.setLayoutParams(titleParams); mainLayout.addView(titleView); - final int qualitySize = qualities.size(); - List qualityLabels = new ArrayList<>(qualitySize); - for (int i = 0; i < qualitySize; i++) { - VideoQuality quality = qualities.get(i); - if (quality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { - qualityLabels.add(quality.patch_getQualityName()); - } - } - - if (qualityLabels.isEmpty()) { - Logger.printDebug(() -> "showVideoQualityDialog: No valid video qualities available"); - dialog.dismiss(); - return; - } - ListView listView = new ListView(context); CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); adapter.setSelectedPosition(listViewSelectedIndex); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 8d0590fca4..269fc28054 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -30,11 +30,8 @@ public static void updateButtonIcon() { try { if (instance == null) return; - //noinspection ExtractMethodRecommender - final int currentQuality = RememberVideoQualityPatch.getDefaultVideoQuality(); - // Map quality to appropriate icon. - String iconResource = switch (currentQuality) { + String iconResource = switch (RememberVideoQualityPatch.getDefaultVideoQuality()) { case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; case 720 -> "revanced_video_quality_dialog_button_hd"; case 1080 -> "revanced_video_quality_dialog_button_fhd"; @@ -46,7 +43,6 @@ public static void updateButtonIcon() { if (!iconResource.equals(currentIconResource)) { currentIconResource = iconResource; instance.setIcon(iconResource); - Logger.printDebug(() -> "Updated button icon to: " + iconResource); } } catch (Exception ex) { Logger.printException(() -> "Failed to update button icon", ex); From 7e1946f2271a69f53f524b9bb9db71755ceed458 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 07:53:38 -0400 Subject: [PATCH 14/57] refactor --- .../quality/RememberVideoQualityPatch.java | 36 +++++++++---------- .../videoplayer/VideoQualityDialogButton.java | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index a926bbb0f9..e631313e00 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -78,7 +78,7 @@ public interface VideoQualityMenuInterface { * The available qualities of the current video. */ @Nullable - private static List videoQualities; + private static List currentQualities; /** * The current VideoQualityMenuInterface, set during setVideoQuality. @@ -101,13 +101,13 @@ public static int getDefaultVideoQuality() { } @Nullable - public static VideoQualityMenuInterface getCurrentMenuInterface() { - return currentMenuInterface; + public static List getCurrentQualities() { + return currentQualities; } @Nullable - public static List getVideoQualities() { - return videoQualities; + public static VideoQualityMenuInterface getCurrentMenuInterface() { + return currentMenuInterface; } private static void changeDefaultQuality(int qualityResolution) { @@ -149,24 +149,20 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentMenuInterface = menu; // Later used by player quality button. - final boolean useShortsPreference = ShortsPlayerState.isOpen(); - final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE - ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() - : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); - - final boolean qualitiesChanged = videoQualities == null || videoQualities.size() != qualities.length; + final boolean qualitiesChanged = currentQualities == null || currentQualities.size() != qualities.length; if (qualitiesChanged) { - videoQualities = Arrays.asList(qualities); - Logger.printDebug(() -> "VideoQualities: " + videoQualities); + currentQualities = Arrays.asList(qualities); + Logger.printDebug(() -> "VideoQualities: " + currentQualities); } + final int preferredQuality = getDefaultVideoQuality(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { return originalQualityIndex; // Nothing to do. } if (userChangedDefaultQuality) { userChangedDefaultQuality = false; - VideoQuality quality = videoQualities.get(userSelectedQualityIndex); + VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); changeDefaultQuality(quality.patch_getResolution()); return userSelectedQualityIndex; @@ -180,10 +176,10 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte qualityNeedsUpdating = false; // Find the highest quality that is equal to or less than the preferred. - VideoQuality qualityToUse = videoQualities.get(0); // First element is automatic mode. + VideoQuality qualityToUse = qualities[0]; // First element is automatic mode. int qualityIndexToUse = 0; int i = 0; - for (VideoQuality quality : videoQualities) { + for (VideoQuality quality : qualities) { final int qualityResolution = quality.patch_getResolution(); if (qualityResolution > qualityToUse.patch_getResolution() && qualityResolution <= preferredQuality) { qualityToUse = quality; @@ -200,7 +196,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal); } else { Logger.printDebug(() -> "Changing video quality from: " - + videoQualities.get(originalQualityIndex) + " to: " + qualityToUseFinal); + + qualities[originalQualityIndex] + " to: " + qualityToUseFinal); } // On first load of a new video, if the video is already the desired quality @@ -233,7 +229,7 @@ public static int fixVideoQualityResolution(String name, int quality) { /** * Injection point. - * @param qualityIndex Element index of {@link #videoQualities}. + * @param qualityIndex Element index of {@link #currentQualities}. */ public static void userChangedQuality(int qualityIndex) { if (shouldRememberVideoQuality()) { @@ -261,7 +257,7 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; - videoQualities = null; + currentQualities = null; currentMenuInterface = null; } @@ -270,7 +266,7 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl */ public static void showVideoQualityDialog(Context context) { try { - List qualities = videoQualities; + List qualities = currentQualities; if (qualities == null) { Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); return; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 269fc28054..06de583aa0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -75,7 +75,7 @@ public static void initializeButton(View controlsView) { // Apply automatic quality immediately. RememberVideoQualityPatch.VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); - List qualities = RememberVideoQualityPatch.getVideoQualities(); + List qualities = RememberVideoQualityPatch.getCurrentQualities(); if (menu != null && qualities != null) { menu.patch_setMenuIndexFromQuality(qualities.get(0)); // Auto is index 0. Logger.printDebug(() -> "Applied automatic quality via long press"); From e1344bf18f5ad1820f5cc9aa3d42e4b3b9994f53 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:34:00 -0400 Subject: [PATCH 15/57] refactor --- .../app/revanced/extension/shared/Utils.java | 10 +++ .../quality/RememberVideoQualityPatch.java | 81 +++++++++---------- .../speed/CustomPlaybackSpeedPatch.java | 8 +- .../ReVancedPreferenceFragment.java | 6 +- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index e84ac36a23..173de404a9 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -1460,6 +1460,16 @@ public static int percentageWidthToPixels(int percentage) { return (int) (metrics.widthPixels * (percentage / 100.0f)); } + /** + * Uses {@link #adjustColorBrightness(int, float)} depending if dark mode is enabled or not. + */ + @ColorInt + public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) { + return isDarkModeEnabled() + ? adjustColorBrightness(baseColor, darkThemeFactor) + : adjustColorBrightness(baseColor, lightThemeFactor); + } + /** * Adjusts the brightness of a color by lightening or darkening it based on the given factor. *

diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index e631313e00..7df50b0a2c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -43,6 +43,7 @@ import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.ShortsPlayerState; +import app.revanced.extension.youtube.videoplayer.VideoQualityDialogButton; @SuppressWarnings("unused") public class RememberVideoQualityPatch { @@ -80,6 +81,12 @@ public interface VideoQualityMenuInterface { @Nullable private static List currentQualities; + /** + * The current quality of the video playing. + */ + @Nullable + private static VideoQuality currentQuality; + /** * The current VideoQualityMenuInterface, set during setVideoQuality. */ @@ -105,6 +112,11 @@ public static List getCurrentQualities() { return currentQualities; } + @Nullable + public static VideoQuality getCurrentQuality() { + return currentQuality; + } + @Nullable public static VideoQualityMenuInterface getCurrentMenuInterface() { return currentMenuInterface; @@ -154,6 +166,11 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQualities = Arrays.asList(qualities); Logger.printDebug(() -> "VideoQualities: " + currentQualities); } + VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; + if (currentQuality != updatedCurrentQuality) { + currentQuality = updatedCurrentQuality; + VideoQualityDialogButton.updateButtonIcon(); + } final int preferredQuality = getDefaultVideoQuality(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { @@ -258,6 +275,7 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; currentQualities = null; + currentQuality = null; currentMenuInterface = null; } @@ -266,44 +284,29 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl */ public static void showVideoQualityDialog(Context context) { try { - List qualities = currentQualities; - if (qualities == null) { + if (currentQualities == null || currentQuality == null) { Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); return; } - VideoQualityMenuInterface menu = currentMenuInterface; - if (menu == null) { + if (currentMenuInterface == null) { Logger.printDebug(() -> "Cannot show qualities dialog, menu is null"); return; } - - String currentQualityLabel = str("video_quality_quick_menu_auto_toast"); - final int currentQuality = getDefaultVideoQuality(); - int listViewSelectedIndex = -1; - if (currentQuality != AUTOMATIC_VIDEO_QUALITY_VALUE) { - int i = 0; - for (VideoQuality quality : qualities) { - if (quality.patch_getResolution() == currentQuality) { - currentQualityLabel = quality.patch_getQualityName(); - listViewSelectedIndex = i - 1; // Adjust for automatic quality at first index. - break; - } - i++; - } + if (currentQualities.size() < 2) { + // Should never happen. + Logger.printDebug(() -> "Cannot show qualities dialog, no qualities available"); + return; } - final int qualitySize = qualities.size(); - List qualityLabels = new ArrayList<>(qualitySize); - for (int i = 0; i < qualitySize; i++) { - VideoQuality quality = qualities.get(i); - if (quality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { - qualityLabels.add(quality.patch_getQualityName()); - } - } + // -1 adjustment for automatic quality at first index. + final int listViewSelectedIndex = currentQualities.indexOf(currentQuality) - 1; - if (qualityLabels.isEmpty()) { - Logger.printException(() -> "Video has no available qualities"); - return; + final int qualitySize = currentQualities.size(); + List qualityLabels = new ArrayList<>(qualitySize - 1); + for (VideoQuality availableQuality : currentQualities) { + if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { + qualityLabels.add(availableQuality.patch_getQualityName()); + } } Dialog dialog = new Dialog(context); @@ -367,11 +370,11 @@ public static void showVideoQualityDialog(Context context) { // Append quality label with adjusted title color. final int qualityStart = spannableTitle.length(); - spannableTitle.append(currentQualityLabel); + spannableTitle.append(currentQuality.patch_getQualityName()); spannableTitle.setSpan( new ForegroundColorSpan(getAdjustedTitleForegroundColor()), qualityStart, - qualityStart + currentQualityLabel.length(), + qualityStart + currentQuality.patch_getQualityName().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); @@ -396,10 +399,10 @@ public static void showVideoQualityDialog(Context context) { listView.setOnItemClickListener((parent, view, which, id) -> { try { - final int originalIndex = which + 1; - VideoQuality selectedQuality = qualities.get(originalIndex); - menu.patch_setMenuIndexFromQuality(selectedQuality); - Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality + " index: " + originalIndex); + final int originalIndex = which + 1; // Adjust for automatic. + VideoQuality selectedQuality = currentQualities.get(originalIndex); + currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); + Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality); if (shouldRememberVideoQuality()) { changeDefaultQuality(selectedQuality.patch_getResolution()); @@ -560,15 +563,11 @@ private static class ViewHolder { public static int getAdjustedHandleBarBackgroundColor() { final int baseColor = Utils.getDialogBackgroundColor(); - return Utils.isDarkModeEnabled() - ? Utils.adjustColorBrightness(baseColor, 1.25f) - : Utils.adjustColorBrightness(baseColor, 0.9f); + return Utils.adjustColorBrightness(baseColor, 0.9f, 1.25f); } public static int getAdjustedTitleForegroundColor() { final int baseColor = Utils.getAppForegroundColor(); - return Utils.isDarkModeEnabled() - ? Utils.adjustColorBrightness(baseColor, 0.6f) - : Utils.adjustColorBrightness(baseColor, 1.6f); + return Utils.adjustColorBrightness(baseColor, 0.6f, 1.6f); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java index 88eae4a0d3..e738be34dc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -671,11 +671,9 @@ private static float roundSpeedToNearestIncrement(float speed) { */ public static int getAdjustedBackgroundColor(boolean isHandleBar) { final int baseColor = Utils.getDialogBackgroundColor(); - float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme. - float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme. - return Utils.isDarkModeEnabled() - ? Utils.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme. - : Utils.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme. + final float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme. + final float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme. + return Utils.adjustColorBrightness(baseColor, lightThemeFactor, darkThemeFactor); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index 02f8db6cb7..c4cb423137 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -333,10 +333,8 @@ static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern return text; } - final int baseColor = Utils.getAppBackgroundColor(); - final int adjustedColor = Utils.isDarkModeEnabled() - ? Utils.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme. - : Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme. + final int adjustedColor = Utils.adjustColorBrightness(Utils.getAppBackgroundColor(), + 0.95f, 1.20f); BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor); SpannableStringBuilder spannable = new SpannableStringBuilder(text); From f754320308f3b2f5a52d2b373dcdbbe79c5c19af Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:34:03 -0400 Subject: [PATCH 16/57] Show current video quality as button icon --- .../videoplayer/VideoQualityDialogButton.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 06de583aa0..ecc9cca616 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -30,8 +30,13 @@ public static void updateButtonIcon() { try { if (instance == null) return; + VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); + final int resolution = currentQuality == null + ? RememberVideoQualityPatch.getDefaultVideoQuality() + : currentQuality.patch_getResolution(); + // Map quality to appropriate icon. - String iconResource = switch (RememberVideoQualityPatch.getDefaultVideoQuality()) { + String iconResource = switch (resolution) { case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; case 720 -> "revanced_video_quality_dialog_button_hd"; case 1080 -> "revanced_video_quality_dialog_button_fhd"; @@ -72,11 +77,12 @@ public static void initializeButton(View controlsView) { // Reset to automatic quality. RememberVideoQualityPatch.userChangedQualityInFlyout( RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE); + // Apply automatic quality immediately. + List qualities = RememberVideoQualityPatch.getCurrentQualities(); RememberVideoQualityPatch.VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); - List qualities = RememberVideoQualityPatch.getCurrentQualities(); - if (menu != null && qualities != null) { + if (qualities != null && menu != null) { menu.patch_setMenuIndexFromQuality(qualities.get(0)); // Auto is index 0. Logger.printDebug(() -> "Applied automatic quality via long press"); } From 3ceb34f18612843c012e48732dec200d0b4f574b Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:57:23 -0400 Subject: [PATCH 17/57] refactor. Button can still flicker when resuming a previously watched video --- .../quality/RememberVideoQualityPatch.java | 46 ++++++++----------- .../videoplayer/VideoQualityDialogButton.java | 4 +- .../innertube/model/media/VideoQuality.java | 10 ++-- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 7df50b0a2c..b77b42faf0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -166,9 +166,11 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQualities = Arrays.asList(qualities); Logger.printDebug(() -> "VideoQualities: " + currentQualities); } + VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; if (currentQuality != updatedCurrentQuality) { currentQuality = updatedCurrentQuality; + Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); VideoQualityDialogButton.updateButtonIcon(); } @@ -193,41 +195,33 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte qualityNeedsUpdating = false; // Find the highest quality that is equal to or less than the preferred. - VideoQuality qualityToUse = qualities[0]; // First element is automatic mode. - int qualityIndexToUse = 0; int i = 0; for (VideoQuality quality : qualities) { final int qualityResolution = quality.patch_getResolution(); - if (qualityResolution > qualityToUse.patch_getResolution() && qualityResolution <= preferredQuality) { - qualityToUse = quality; - qualityIndexToUse = i; - break; + if (qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality) { + // If the desired quality index is equal to the original index, + // then the video is already set to the desired default quality. + if (i == originalQualityIndex) { + Logger.printDebug(() -> "Video is already preferred quality: " + quality); + } else { + Logger.printDebug(() -> "Changing video quality from: " + + qualities[originalQualityIndex] + " to: " + quality); + } + + // On first load of a new video, if the video is already the desired quality + // then the quality flyout will show 'Auto' (ie: Auto (720p)). + // + // To prevent user confusion, set the video index even if the + // quality is already correct so the UI picker will not display "Auto". + menu.patch_setMenuIndexFromQuality(qualities[i]); + return i; } i++; } - - // If the desired quality index is equal to the original index, - // then the video is already set to the desired default quality. - VideoQuality qualityToUseFinal = qualityToUse; - if (qualityIndexToUse == originalQualityIndex) { - Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal); - } else { - Logger.printDebug(() -> "Changing video quality from: " - + qualities[originalQualityIndex] + " to: " + qualityToUseFinal); - } - - // On first load of a new video, if the video is already the desired quality - // then the quality flyout will show 'Auto' (ie: Auto (720p)). - // - // To prevent user confusion, set the video index even if the - // quality is already correct so the UI picker will not display "Auto". - menu.patch_setMenuIndexFromQuality(qualities[qualityIndexToUse]); - - return qualityIndexToUse; } catch (Exception ex) { Logger.printException(() -> "setVideoQuality failure", ex); - return originalQualityIndex; } + return originalQualityIndex; } /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index ecc9cca616..7bba999e22 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -32,7 +32,7 @@ public static void updateButtonIcon() { VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); final int resolution = currentQuality == null - ? RememberVideoQualityPatch.getDefaultVideoQuality() + ? RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE : currentQuality.patch_getResolution(); // Map quality to appropriate icon. @@ -50,7 +50,7 @@ public static void updateButtonIcon() { instance.setIcon(iconResource); } } catch (Exception ex) { - Logger.printException(() -> "Failed to update button icon", ex); + Logger.printException(() -> "updateButtonIcon failure", ex); } } diff --git a/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java b/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java index 67dbc6873b..628c423abe 100644 --- a/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java +++ b/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java @@ -1,12 +1,8 @@ package com.google.android.libraries.youtube.innertube.model.media; -public class VideoQuality { - public final String patch_getQualityName() { - throw new UnsupportedOperationException("Stub"); - } +public abstract class VideoQuality implements Comparable { + public abstract String patch_getQualityName(); - public final int patch_getResolution() { - throw new UnsupportedOperationException("Stub"); - } + public abstract int patch_getResolution(); } From 740a0ead0a745227ba1be6630fa1f6fb0bd22e28 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:29:14 +0300 Subject: [PATCH 18/57] fix getAdjustedTitleForegroundColor --- .../patches/playback/quality/RememberVideoQualityPatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index b77b42faf0..e942b92525 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -562,6 +562,6 @@ public static int getAdjustedHandleBarBackgroundColor() { public static int getAdjustedTitleForegroundColor() { final int baseColor = Utils.getAppForegroundColor(); - return Utils.adjustColorBrightness(baseColor, 0.6f, 1.6f); + return Utils.adjustColorBrightness(baseColor, 1.6f, 0.6f); } } From 7c7b2f529869f4d98d6a292d4e6c88266516c1e0 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:49:13 +0300 Subject: [PATCH 19/57] use debouncing (revert if it doesn't work) --- .../quality/RememberVideoQualityPatch.java | 2 ++ .../videoplayer/VideoQualityDialogButton.java | 24 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index e942b92525..eb63424870 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -271,6 +271,8 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentQualities = null; currentQuality = null; currentMenuInterface = null; + + VideoQualityDialogButton.cleanup(); } /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 7bba999e22..4c38455cda 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.videoplayer; +import android.os.Handler; +import android.os.Looper; import android.view.View; import androidx.annotation.Nullable; @@ -23,6 +25,9 @@ public class VideoQualityDialogButton { @Nullable private static String currentIconResource; + private static final Handler handler = new Handler(Looper.getMainLooper()); + private static Runnable updateIconRunnable; + /** * Updates the button icon based on the current video quality. */ @@ -46,14 +51,29 @@ public static void updateButtonIcon() { }; if (!iconResource.equals(currentIconResource)) { - currentIconResource = iconResource; - instance.setIcon(iconResource); + if (updateIconRunnable != null) { + handler.removeCallbacks(updateIconRunnable); + } + + updateIconRunnable = () -> { + currentIconResource = iconResource; + instance.setIcon(iconResource); + }; + + handler.postDelayed(updateIconRunnable, 300); } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); } } + public static void cleanup() { + if (updateIconRunnable != null) { + handler.removeCallbacks(updateIconRunnable); + updateIconRunnable = null; + } + } + /** * Injection point. */ From 3c5b7fa1aec17bb3cfdfdfe4eecabe9897897929 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:00:21 -0400 Subject: [PATCH 20/57] Long tap to reset to default --- .../quality/RememberVideoQualityPatch.java | 14 +++++--- .../videoplayer/VideoQualityDialogButton.java | 35 +++++++++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index eb63424870..bc6bd6379a 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -100,7 +100,7 @@ private static boolean shouldRememberVideoQuality() { return preference.get(); } - public static int getDefaultVideoQuality() { + public static int getDefaultQualityResolution() { final boolean isShorts = ShortsPlayerState.isOpen(); return Utils.getNetworkType() == NetworkType.MOBILE ? (isShorts ? shortsQualityMobile : videoQualityMobile).get() @@ -167,14 +167,20 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte Logger.printDebug(() -> "VideoQualities: " + currentQualities); } + boolean currentQualityChanged = false; VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; - if (currentQuality != updatedCurrentQuality) { + if (currentQuality == null + || currentQuality.patch_getResolution() != updatedCurrentQuality.patch_getResolution()) { currentQuality = updatedCurrentQuality; + currentQualityChanged = true; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); + } + + if (qualitiesChanged || currentQualityChanged) { VideoQualityDialogButton.updateButtonIcon(); } - final int preferredQuality = getDefaultVideoQuality(); + final int preferredQuality = getDefaultQualityResolution(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { return originalQualityIndex; // Nothing to do. } @@ -230,8 +236,6 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte public static int fixVideoQualityResolution(String name, int quality) { final int correctQuality = 480; if (name.equals("480p") && quality != correctQuality) { - Logger.printDebug(() -> "Fixing bad data of " + name + " from: " + quality - + " to: " + correctQuality); return correctQuality; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 4c38455cda..dbb0ee3f72 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.videoplayer; +import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE; + import android.os.Handler; import android.os.Looper; import android.view.View; @@ -37,7 +39,7 @@ public static void updateButtonIcon() { VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); final int resolution = currentQuality == null - ? RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE + ? AUTOMATIC_VIDEO_QUALITY_VALUE : currentQuality.patch_getResolution(); // Map quality to appropriate icon. @@ -87,27 +89,38 @@ public static void initializeButton(View controlsView) { view -> { try { RememberVideoQualityPatch.showVideoQualityDialog(view.getContext()); - updateButtonIcon(); // Update icon after dialog interaction. + updateButtonIcon(); } catch (Exception ex) { Logger.printException(() -> "Video quality button onClick failure", ex); } }, view -> { try { - // Reset to automatic quality. - RememberVideoQualityPatch.userChangedQualityInFlyout( - RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE); - - // Apply automatic quality immediately. List qualities = RememberVideoQualityPatch.getCurrentQualities(); RememberVideoQualityPatch.VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); - if (qualities != null && menu != null) { - menu.patch_setMenuIndexFromQuality(qualities.get(0)); // Auto is index 0. - Logger.printDebug(() -> "Applied automatic quality via long press"); + if (qualities == null || menu == null) { + Logger.printDebug(() -> "Cannot reset quality, videoQualities is null"); + return true; + } + + // Reset to default quality. + VideoQuality resetQuality = qualities.get(0); + int resetIndex = 0; + final int defaultResolution = RememberVideoQualityPatch.getDefaultQualityResolution(); + for (VideoQuality quality : qualities) { + final int resolution = quality.patch_getResolution(); + if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { + resetQuality = quality; + break; + } + resetIndex++; } - updateButtonIcon(); // Update icon after reset. + VideoQuality resetQualityFinal = resetQuality; + Logger.printDebug(() -> "Resetting quality to: " + resetQualityFinal); + menu.patch_setMenuIndexFromQuality(resetQuality); + updateButtonIcon(); return true; } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); From 194d13ae30ead4c1e4f0db82a2b5a5d30d2e3f63 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:11:30 -0400 Subject: [PATCH 21/57] Move button dialog to button class --- .../quality/RememberVideoQualityPatch.java | 324 +----------------- .../videoplayer/VideoQualityDialogButton.java | 319 ++++++++++++++++- 2 files changed, 321 insertions(+), 322 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index bc6bd6379a..aeb983538b 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -2,39 +2,13 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.NetworkType; -import static app.revanced.extension.shared.Utils.dipToPixels; - -import android.app.Dialog; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.style.ForegroundColorSpan; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.LayoutInflater; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.ImageView; -import android.widget.ArrayAdapter; - -import androidx.annotation.NonNull; + import androidx.annotation.Nullable; import com.google.android.libraries.youtube.innertube.model.media.VideoQuality; import java.util.Arrays; import java.util.List; -import java.util.ArrayList; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; @@ -93,7 +67,7 @@ public interface VideoQualityMenuInterface { @Nullable private static VideoQualityMenuInterface currentMenuInterface; - private static boolean shouldRememberVideoQuality() { + public static boolean shouldRememberVideoQuality() { BooleanSetting preference = ShortsPlayerState.isOpen() ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; @@ -122,7 +96,7 @@ public static VideoQualityMenuInterface getCurrentMenuInterface() { return currentMenuInterface; } - private static void changeDefaultQuality(int qualityResolution) { + public static void changeDefaultQuality(int qualityResolution) { final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; IntegerSetting qualitySetting; @@ -278,296 +252,4 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl VideoQualityDialogButton.cleanup(); } - - /** - * Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality. - */ - public static void showVideoQualityDialog(Context context) { - try { - if (currentQualities == null || currentQuality == null) { - Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); - return; - } - if (currentMenuInterface == null) { - Logger.printDebug(() -> "Cannot show qualities dialog, menu is null"); - return; - } - if (currentQualities.size() < 2) { - // Should never happen. - Logger.printDebug(() -> "Cannot show qualities dialog, no qualities available"); - return; - } - - // -1 adjustment for automatic quality at first index. - final int listViewSelectedIndex = currentQualities.indexOf(currentQuality) - 1; - - final int qualitySize = currentQualities.size(); - List qualityLabels = new ArrayList<>(qualitySize - 1); - for (VideoQuality availableQuality : currentQualities) { - if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { - qualityLabels.add(availableQuality.patch_getQualityName()); - } - } - - Dialog dialog = new Dialog(context); - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - dialog.setCanceledOnTouchOutside(true); - dialog.setCancelable(true); - - final int dip4 = dipToPixels(4); // Height for handle bar. - final int dip5 = dipToPixels(5); // Padding for mainLayout. - final int dip6 = dipToPixels(6); // Bottom margin. - final int dip8 = dipToPixels(8); // Side padding. - final int dip16 = dipToPixels(16); // Left padding for ListView. - final int dip20 = dipToPixels(20); // Margin below handle. - final int dip40 = dipToPixels(40); // Width for handle bar. - - LinearLayout mainLayout = new LinearLayout(context); - mainLayout.setOrientation(LinearLayout.VERTICAL); - mainLayout.setPadding(dip5, dip8, dip5, dip8); - - ShapeDrawable background = new ShapeDrawable(new RoundRectShape( - Utils.createCornerRadii(12), null, null)); - background.getPaint().setColor(Utils.getDialogBackgroundColor()); - mainLayout.setBackground(background); - - View handleBar = new View(context); - ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( - Utils.createCornerRadii(4), null, null)); - handleBackground.getPaint().setColor(getAdjustedHandleBarBackgroundColor()); - handleBar.setBackground(handleBackground); - LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4); - handleParams.gravity = Gravity.CENTER_HORIZONTAL; - handleParams.setMargins(0, 0, 0, dip20); - handleBar.setLayoutParams(handleParams); - mainLayout.addView(handleBar); - - // Create SpannableStringBuilder for formatted text. - SpannableStringBuilder spannableTitle = new SpannableStringBuilder(); - String titlePart = str("video_quality_quick_menu_title"); - String separatorPart = str("video_quality_title_seperator"); - - // Append title part with default foreground color. - spannableTitle.append(titlePart); - spannableTitle.setSpan( - new ForegroundColorSpan(Utils.getAppForegroundColor()), - 0, - titlePart.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ); - spannableTitle.append(" "); // Space after title. - - // Append separator part with adjusted title color. - int separatorStart = spannableTitle.length(); - spannableTitle.append(separatorPart); - spannableTitle.setSpan( - new ForegroundColorSpan(getAdjustedTitleForegroundColor()), - separatorStart, - separatorStart + separatorPart.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ); - spannableTitle.append(" "); // Space after separator. - - // Append quality label with adjusted title color. - final int qualityStart = spannableTitle.length(); - spannableTitle.append(currentQuality.patch_getQualityName()); - spannableTitle.setSpan( - new ForegroundColorSpan(getAdjustedTitleForegroundColor()), - qualityStart, - qualityStart + currentQuality.patch_getQualityName().length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ); - - // Add title with current quality. - TextView titleView = new TextView(context); - titleView.setText(spannableTitle); - titleView.setTextSize(16); - // Remove setTextColor since color is handled by SpannableStringBuilder. - LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - titleParams.setMargins(dip8, 0, 0, dip20); - titleView.setLayoutParams(titleParams); - mainLayout.addView(titleView); - - ListView listView = new ListView(context); - CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); - adapter.setSelectedPosition(listViewSelectedIndex); - listView.setAdapter(adapter); - listView.setDivider(null); - listView.setPadding(dip16, 0, 0, 0); - - listView.setOnItemClickListener((parent, view, which, id) -> { - try { - final int originalIndex = which + 1; // Adjust for automatic. - VideoQuality selectedQuality = currentQualities.get(originalIndex); - currentMenuInterface.patch_setMenuIndexFromQuality(selectedQuality); - Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality); - - if (shouldRememberVideoQuality()) { - changeDefaultQuality(selectedQuality.patch_getResolution()); - } - - dialog.dismiss(); - } catch (Exception ex) { - Logger.printException(() -> "Video quality selection failure", ex); - } - }); - - LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - listViewParams.setMargins(0, 0, 0, dip5); - listView.setLayoutParams(listViewParams); - mainLayout.addView(listView); - - LinearLayout wrapperLayout = new LinearLayout(context); - wrapperLayout.setOrientation(LinearLayout.VERTICAL); - wrapperLayout.setPadding(dip8, 0, dip8, 0); - wrapperLayout.addView(mainLayout); - dialog.setContentView(wrapperLayout); - - Window window = dialog.getWindow(); - if (window != null) { - WindowManager.LayoutParams params = window.getAttributes(); - params.gravity = Gravity.BOTTOM; - params.y = dip6; - int portraitWidth = context.getResources().getDisplayMetrics().widthPixels; - if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - portraitWidth = Math.min( - portraitWidth, - context.getResources().getDisplayMetrics().heightPixels); - } - params.width = portraitWidth; - params.height = WindowManager.LayoutParams.WRAP_CONTENT; - window.setAttributes(params); - window.setBackgroundDrawable(null); - } - - final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast"); - Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom"); - slideInABottomAnimation.setDuration(fadeDurationFast); - mainLayout.startAnimation(slideInABottomAnimation); - - // noinspection ClickableViewAccessibility - mainLayout.setOnTouchListener(new View.OnTouchListener() { - final float dismissThreshold = dipToPixels(100); - float touchY; - float translationY; - - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - touchY = event.getRawY(); - translationY = mainLayout.getTranslationY(); - return true; - case MotionEvent.ACTION_MOVE: - final float deltaY = event.getRawY() - touchY; - if (deltaY >= 0) { - mainLayout.setTranslationY(translationY + deltaY); - } - return true; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mainLayout.getTranslationY() > dismissThreshold) { - //noinspection ExtractMethodRecommender - final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels - - mainLayout.getTop(); - TranslateAnimation slideOut = new TranslateAnimation( - 0, 0, mainLayout.getTranslationY(), remainingDistance); - slideOut.setDuration(fadeDurationFast); - slideOut.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) {} - @Override - public void onAnimationEnd(Animation animation) { - dialog.dismiss(); - } - @Override - public void onAnimationRepeat(Animation animation) {} - }); - mainLayout.startAnimation(slideOut); - } else { - TranslateAnimation slideBack = new TranslateAnimation( - 0, 0, mainLayout.getTranslationY(), 0); - slideBack.setDuration(fadeDurationFast); - mainLayout.startAnimation(slideBack); - mainLayout.setTranslationY(0); - } - return true; - default: - return false; - } - } - }); - - dialog.show(); - } catch (Exception ex) { - Logger.printException(() -> "showVideoQualityDialog failure", ex); - } - } - - public static class CustomQualityAdapter extends ArrayAdapter { - private int selectedPosition = -1; - - public CustomQualityAdapter(@NonNull Context context, @NonNull List objects) { - super(context, 0, objects); - } - - public void setSelectedPosition(int position) { - this.selectedPosition = position; - notifyDataSetChanged(); - } - - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - ViewHolder viewHolder; - - if (convertView == null) { - convertView = LayoutInflater.from(getContext()).inflate( - Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"), - parent, - false - ); - viewHolder = new ViewHolder(); - viewHolder.checkIcon = convertView.findViewById( - Utils.getResourceIdentifier("revanced_check_icon", "id") - ); - viewHolder.placeholder = convertView.findViewById( - Utils.getResourceIdentifier("revanced_check_icon_placeholder", "id") - ); - viewHolder.textView = convertView.findViewById( - Utils.getResourceIdentifier("revanced_item_text", "id") - ); - convertView.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - - viewHolder.textView.setText(getItem(position)); - final boolean isSelected = position == selectedPosition; - viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); - viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE); - - return convertView; - } - - private static class ViewHolder { - ImageView checkIcon; - View placeholder; - TextView textView; - } - } - - public static int getAdjustedHandleBarBackgroundColor() { - final int baseColor = Utils.getDialogBackgroundColor(); - return Utils.adjustColorBrightness(baseColor, 0.9f, 1.25f); - } - - public static int getAdjustedTitleForegroundColor() { - final int baseColor = Utils.getAppForegroundColor(); - return Utils.adjustColorBrightness(baseColor, 1.6f, 0.6f); - } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index dbb0ee3f72..172cfe1dc3 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -1,18 +1,44 @@ package app.revanced.extension.youtube.videoplayer; +import static app.revanced.extension.shared.StringRef.str; +import static app.revanced.extension.shared.Utils.dipToPixels; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; import android.os.Handler; import android.os.Looper; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.libraries.youtube.innertube.model.media.VideoQuality; +import java.util.ArrayList; import java.util.List; import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch; import app.revanced.extension.youtube.settings.Settings; @@ -37,6 +63,7 @@ public static void updateButtonIcon() { try { if (instance == null) return; + //noinspection ExtractMethodRecommender VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); final int resolution = currentQuality == null ? AUTOMATIC_VIDEO_QUALITY_VALUE @@ -88,7 +115,7 @@ public static void initializeButton(View controlsView) { Settings.VIDEO_QUALITY_DIALOG_BUTTON::get, view -> { try { - RememberVideoQualityPatch.showVideoQualityDialog(view.getContext()); + showVideoQualityDialog(view.getContext()); updateButtonIcon(); } catch (Exception ex) { Logger.printException(() -> "Video quality button onClick failure", ex); @@ -154,4 +181,294 @@ public static void setVisibility(boolean visible, boolean animated) { if (visible) updateButtonIcon(); } } + + /** + * Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality. + */ + private static void showVideoQualityDialog(Context context) { + try { + List currentQualities = RememberVideoQualityPatch.getCurrentQualities(); + VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); + if (currentQualities == null || currentQuality == null) { + Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null"); + return; + } + if (currentQualities.size() < 2) { + // Should never happen. + Logger.printDebug(() -> "Cannot show qualities dialog, no qualities available"); + return; + } + + RememberVideoQualityPatch.VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); + if (menu == null) { + Logger.printDebug(() -> "Cannot show qualities dialog, menu is null"); + return; + } + + // -1 adjustment for automatic quality at first index. + final int listViewSelectedIndex = currentQualities.indexOf(currentQuality) - 1; + + final int qualitySize = currentQualities.size(); + List qualityLabels = new ArrayList<>(qualitySize - 1); + for (VideoQuality availableQuality : currentQualities) { + if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { + qualityLabels.add(availableQuality.patch_getQualityName()); + } + } + + Dialog dialog = new Dialog(context); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setCanceledOnTouchOutside(true); + dialog.setCancelable(true); + + final int dip4 = dipToPixels(4); // Height for handle bar. + final int dip5 = dipToPixels(5); // Padding for mainLayout. + final int dip6 = dipToPixels(6); // Bottom margin. + final int dip8 = dipToPixels(8); // Side padding. + final int dip16 = dipToPixels(16); // Left padding for ListView. + final int dip20 = dipToPixels(20); // Margin below handle. + final int dip40 = dipToPixels(40); // Width for handle bar. + + LinearLayout mainLayout = new LinearLayout(context); + mainLayout.setOrientation(LinearLayout.VERTICAL); + mainLayout.setPadding(dip5, dip8, dip5, dip8); + + ShapeDrawable background = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(12), null, null)); + background.getPaint().setColor(Utils.getDialogBackgroundColor()); + mainLayout.setBackground(background); + + View handleBar = new View(context); + ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( + Utils.createCornerRadii(4), null, null)); + final int baseColor = Utils.getDialogBackgroundColor(); + final int adjustedHandleBarBackgroundColor = Utils.adjustColorBrightness( + baseColor, 0.9f, 1.25f); + handleBackground.getPaint().setColor(adjustedHandleBarBackgroundColor); + handleBar.setBackground(handleBackground); + LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4); + handleParams.gravity = Gravity.CENTER_HORIZONTAL; + handleParams.setMargins(0, 0, 0, dip20); + handleBar.setLayoutParams(handleParams); + mainLayout.addView(handleBar); + + // Create SpannableStringBuilder for formatted text. + SpannableStringBuilder spannableTitle = new SpannableStringBuilder(); + String titlePart = str("video_quality_quick_menu_title"); + String separatorPart = str("video_quality_title_seperator"); + + // Append title part with default foreground color. + spannableTitle.append(titlePart); + spannableTitle.setSpan( + new ForegroundColorSpan(Utils.getAppForegroundColor()), + 0, + titlePart.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + spannableTitle.append(" "); // Space after title. + + // Append separator part with adjusted title color. + int separatorStart = spannableTitle.length(); + spannableTitle.append(separatorPart); + final int adjustedTitleForegroundColor = Utils.adjustColorBrightness(Utils.getAppForegroundColor(), 1.6f, 0.6f); + spannableTitle.setSpan( + new ForegroundColorSpan(adjustedTitleForegroundColor), + separatorStart, + separatorStart + separatorPart.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + spannableTitle.append(" "); // Space after separator. + + // Append quality label with adjusted title color. + final int qualityStart = spannableTitle.length(); + spannableTitle.append(currentQuality.patch_getQualityName()); + spannableTitle.setSpan( + new ForegroundColorSpan(adjustedTitleForegroundColor), + qualityStart, + qualityStart + currentQuality.patch_getQualityName().length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + + // Add title with current quality. + TextView titleView = new TextView(context); + titleView.setText(spannableTitle); + titleView.setTextSize(16); + // Remove setTextColor since color is handled by SpannableStringBuilder. + LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + titleParams.setMargins(dip8, 0, 0, dip20); + titleView.setLayoutParams(titleParams); + mainLayout.addView(titleView); + + ListView listView = new ListView(context); + CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels); + adapter.setSelectedPosition(listViewSelectedIndex); + listView.setAdapter(adapter); + listView.setDivider(null); + listView.setPadding(dip16, 0, 0, 0); + + listView.setOnItemClickListener((parent, view, which, id) -> { + try { + final int originalIndex = which + 1; // Adjust for automatic. + VideoQuality selectedQuality = currentQualities.get(originalIndex); + menu.patch_setMenuIndexFromQuality(selectedQuality); + Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality); + + if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { + RememberVideoQualityPatch.changeDefaultQuality(selectedQuality.patch_getResolution()); + } + + dialog.dismiss(); + } catch (Exception ex) { + Logger.printException(() -> "Video quality selection failure", ex); + } + }); + + LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + listViewParams.setMargins(0, 0, 0, dip5); + listView.setLayoutParams(listViewParams); + mainLayout.addView(listView); + + LinearLayout wrapperLayout = new LinearLayout(context); + wrapperLayout.setOrientation(LinearLayout.VERTICAL); + wrapperLayout.setPadding(dip8, 0, dip8, 0); + wrapperLayout.addView(mainLayout); + dialog.setContentView(wrapperLayout); + + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = Gravity.BOTTOM; + params.y = dip6; + int portraitWidth = context.getResources().getDisplayMetrics().widthPixels; + if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + portraitWidth = Math.min( + portraitWidth, + context.getResources().getDisplayMetrics().heightPixels); + } + params.width = portraitWidth; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(params); + window.setBackgroundDrawable(null); + } + + final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast"); + Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom"); + slideInABottomAnimation.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideInABottomAnimation); + + // noinspection ClickableViewAccessibility + mainLayout.setOnTouchListener(new View.OnTouchListener() { + final float dismissThreshold = dipToPixels(100); + float touchY; + float translationY; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchY = event.getRawY(); + translationY = mainLayout.getTranslationY(); + return true; + case MotionEvent.ACTION_MOVE: + final float deltaY = event.getRawY() - touchY; + if (deltaY >= 0) { + mainLayout.setTranslationY(translationY + deltaY); + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mainLayout.getTranslationY() > dismissThreshold) { + //noinspection ExtractMethodRecommender + final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels + - mainLayout.getTop(); + TranslateAnimation slideOut = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), remainingDistance); + slideOut.setDuration(fadeDurationFast); + slideOut.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + @Override + public void onAnimationEnd(Animation animation) { + dialog.dismiss(); + } + @Override + public void onAnimationRepeat(Animation animation) {} + }); + mainLayout.startAnimation(slideOut); + } else { + TranslateAnimation slideBack = new TranslateAnimation( + 0, 0, mainLayout.getTranslationY(), 0); + slideBack.setDuration(fadeDurationFast); + mainLayout.startAnimation(slideBack); + mainLayout.setTranslationY(0); + } + return true; + default: + return false; + } + } + }); + + dialog.show(); + } catch (Exception ex) { + Logger.printException(() -> "showVideoQualityDialog failure", ex); + } + } + + private static class CustomQualityAdapter extends ArrayAdapter { + private int selectedPosition = -1; + + public CustomQualityAdapter(@NonNull Context context, @NonNull List objects) { + super(context, 0, objects); + } + + public void setSelectedPosition(int position) { + this.selectedPosition = position; + notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate( + Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"), + parent, + false + ); + viewHolder = new ViewHolder(); + viewHolder.checkIcon = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon", "id") + ); + viewHolder.placeholder = convertView.findViewById( + Utils.getResourceIdentifier("revanced_check_icon_placeholder", "id") + ); + viewHolder.textView = convertView.findViewById( + Utils.getResourceIdentifier("revanced_item_text", "id") + ); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + + viewHolder.textView.setText(getItem(position)); + final boolean isSelected = position == selectedPosition; + viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE); + viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE); + + return convertView; + } + + private static class ViewHolder { + ImageView checkIcon; + View placeholder; + TextView textView; + } + } } From 6adfaa94ae4b2918452ef05cf17bd51e2d21018a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:17:50 -0400 Subject: [PATCH 22/57] rename drawable to unknown --- .../playback/quality/RememberVideoQualityPatch.java | 7 ++++--- .../youtube/videoplayer/VideoQualityDialogButton.java | 4 ++-- .../video/quality/button/VideoQualityDialogButtonPatch.kt | 8 ++++---- ...l => revanced_video_quality_dialog_button_unknown.xml} | 0 .../host/layout/youtube_controls_bottom_ui_container.xml | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) rename patches/src/main/resources/qualitybutton/drawable/{revanced_video_quality_dialog_button.xml => revanced_video_quality_dialog_button_unknown.xml} (100%) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index aeb983538b..75cc1eeca9 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -76,9 +76,10 @@ public static boolean shouldRememberVideoQuality() { public static int getDefaultQualityResolution() { final boolean isShorts = ShortsPlayerState.isOpen(); - return Utils.getNetworkType() == NetworkType.MOBILE - ? (isShorts ? shortsQualityMobile : videoQualityMobile).get() - : (isShorts ? shortsQualityWifi : videoQualityWifi).get(); + IntegerSetting preference = Utils.getNetworkType() == NetworkType.MOBILE + ? (isShorts ? shortsQualityMobile : videoQualityMobile) + : (isShorts ? shortsQualityWifi : videoQualityWifi); + return preference.get(); } @Nullable diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 172cfe1dc3..95007f8048 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -66,7 +66,7 @@ public static void updateButtonIcon() { //noinspection ExtractMethodRecommender VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); final int resolution = currentQuality == null - ? AUTOMATIC_VIDEO_QUALITY_VALUE + ? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading. : currentQuality.patch_getResolution(); // Map quality to appropriate icon. @@ -76,7 +76,7 @@ public static void updateButtonIcon() { case 1080 -> "revanced_video_quality_dialog_button_fhd"; case 1440 -> "revanced_video_quality_dialog_button_qhd"; case 2160 -> "revanced_video_quality_dialog_button_4k"; - default -> "revanced_video_quality_dialog_button"; + default -> "revanced_video_quality_dialog_button_unknown"; }; if (!iconResource.equals(currentIconResource)) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index 1a9bc25904..a56c99bea0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -21,12 +21,12 @@ private val videoQualityButtonResourcePatch = resourcePatch { "qualitybutton", ResourceGroup( "drawable", - "revanced_video_quality_dialog_button.xml", - "revanced_video_quality_dialog_button_4k.xml", - "revanced_video_quality_dialog_button_fhd.xml", - "revanced_video_quality_dialog_button_hd.xml", "revanced_video_quality_dialog_button_lhd.xml", + "revanced_video_quality_dialog_button_hd.xml", + "revanced_video_quality_dialog_button_fhd.xml", "revanced_video_quality_dialog_button_qhd.xml", + "revanced_video_quality_dialog_button_4k.xml", + "revanced_video_quality_dialog_button_unknown.xml", ), ) diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml similarity index 100% rename from patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button.xml rename to patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml diff --git a/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml index e26adf5a65..35061848bd 100644 --- a/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -15,7 +15,7 @@ android:paddingBottom="0dp" android:longClickable="false" android:scaleType="center" - android:src="@drawable/revanced_video_quality_dialog_button" + android:src="@drawable/revanced_video_quality_dialog_button_unknown" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" /> From 24719f774209a7baf373483c780771f505c4ae70 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:23:22 -0400 Subject: [PATCH 23/57] For now, ignore long press if default quality is automatic --- .../videoplayer/VideoQualityDialogButton.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 95007f8048..14e0f21d20 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -132,27 +132,20 @@ public static void initializeButton(View controlsView) { } // Reset to default quality. - VideoQuality resetQuality = qualities.get(0); - int resetIndex = 0; final int defaultResolution = RememberVideoQualityPatch.getDefaultQualityResolution(); for (VideoQuality quality : qualities) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { - resetQuality = quality; - break; + Logger.printDebug(() -> "Resetting quality to: " + quality); + menu.patch_setMenuIndexFromQuality(quality); + updateButtonIcon(); + return true; } - resetIndex++; } - - VideoQuality resetQualityFinal = resetQuality; - Logger.printDebug(() -> "Resetting quality to: " + resetQualityFinal); - menu.patch_setMenuIndexFromQuality(resetQuality); - updateButtonIcon(); - return true; } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); - return false; } + return false; } ); From aa1dd06e8656d481b39a588463dbd4588534d151 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:44:32 -0400 Subject: [PATCH 24/57] Open dialog if long press on automatic quality --- .../youtube/videoplayer/VideoQualityDialogButton.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 14e0f21d20..ce9f4654c9 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -53,8 +53,9 @@ public class VideoQualityDialogButton { @Nullable private static String currentIconResource; - private static final Handler handler = new Handler(Looper.getMainLooper()); + @Nullable private static Runnable updateIconRunnable; + private static final Handler handler = new Handler(Looper.getMainLooper()); /** * Updates the button icon based on the current video quality. @@ -142,6 +143,11 @@ public static void initializeButton(View controlsView) { return true; } } + + // Existing hook cannot set default quality to auto. + // Instead show the quality dialog. + showVideoQualityDialog(view.getContext()); + updateButtonIcon(); } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); } From 946d9f5227e68c4241a1c67d134f4fac839a7d4c Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:11:42 -0400 Subject: [PATCH 25/57] Fix delayed button update after clicking --- .../quality/RememberVideoQualityPatch.java | 20 ++++++------- .../videoplayer/VideoQualityDialogButton.java | 29 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 75cc1eeca9..ae9aa012a0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -133,26 +133,24 @@ public static void changeDefaultQuality(int qualityResolution) { public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) { try { Utils.verifyOnMainThread(); + currentMenuInterface = menu; - currentMenuInterface = menu; // Later used by player quality button. - - final boolean qualitiesChanged = currentQualities == null || currentQualities.size() != qualities.length; - if (qualitiesChanged) { + final boolean availableQualitiesChanged = currentQualities == null + || currentQualities.size() != qualities.length; + if (availableQualitiesChanged) { currentQualities = Arrays.asList(qualities); Logger.printDebug(() -> "VideoQualities: " + currentQualities); } - boolean currentQualityChanged = false; VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; if (currentQuality == null || currentQuality.patch_getResolution() != updatedCurrentQuality.patch_getResolution()) { currentQuality = updatedCurrentQuality; - currentQualityChanged = true; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - } - if (qualitiesChanged || currentQualityChanged) { - VideoQualityDialogButton.updateButtonIcon(); + if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); + } } final int preferredQuality = getDefaultQualityResolution(); @@ -165,12 +163,13 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); changeDefaultQuality(quality.patch_getResolution()); + VideoQualityDialogButton.updateButtonIcon(quality, true); return userSelectedQualityIndex; } // After changing videos the qualities can initially be for the prior video. // If the qualities have changed and the default is not auto then an update is needed. - if (!qualityNeedsUpdating && !qualitiesChanged) { + if (!qualityNeedsUpdating && !availableQualitiesChanged) { return originalQualityIndex; } qualityNeedsUpdating = false; @@ -194,6 +193,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte // // To prevent user confusion, set the video index even if the // quality is already correct so the UI picker will not display "Auto". + Logger.printDebug(() -> "Changing quality to default: " + quality); menu.patch_setMenuIndexFromQuality(qualities[i]); return i; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index ce9f4654c9..dc0ba1e023 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -60,12 +60,10 @@ public class VideoQualityDialogButton { /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon() { + public static void updateButtonIcon(@Nullable VideoQuality currentQuality, boolean updateImmediately) { try { if (instance == null) return; - //noinspection ExtractMethodRecommender - VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality(); final int resolution = currentQuality == null ? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading. : currentQuality.patch_getResolution(); @@ -81,16 +79,19 @@ public static void updateButtonIcon() { }; if (!iconResource.equals(currentIconResource)) { - if (updateIconRunnable != null) { - handler.removeCallbacks(updateIconRunnable); - } + cleanup(); - updateIconRunnable = () -> { + Runnable update = () -> { currentIconResource = iconResource; instance.setIcon(iconResource); }; - handler.postDelayed(updateIconRunnable, 300); + if (updateImmediately) { + update.run(); + } else { + updateIconRunnable = update; + handler.postDelayed(update, 300); + } } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); @@ -117,7 +118,6 @@ public static void initializeButton(View controlsView) { view -> { try { showVideoQualityDialog(view.getContext()); - updateButtonIcon(); } catch (Exception ex) { Logger.printException(() -> "Video quality button onClick failure", ex); } @@ -138,8 +138,8 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); + updateButtonIcon(quality, true); menu.patch_setMenuIndexFromQuality(quality); - updateButtonIcon(); return true; } } @@ -147,7 +147,6 @@ public static void initializeButton(View controlsView) { // Existing hook cannot set default quality to auto. // Instead show the quality dialog. showVideoQualityDialog(view.getContext()); - updateButtonIcon(); } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); } @@ -155,7 +154,8 @@ public static void initializeButton(View controlsView) { } ); - updateButtonIcon(); // Set initial icon. + // Set initial icon. + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -167,7 +167,6 @@ public static void initializeButton(View controlsView) { public static void setVisibilityImmediate(boolean visible) { if (instance != null) { instance.setVisibilityImmediate(visible); - if (visible) updateButtonIcon(); } } @@ -177,7 +176,6 @@ public static void setVisibilityImmediate(boolean visible) { public static void setVisibility(boolean visible, boolean animated) { if (instance != null) { instance.setVisibility(visible, animated); - if (visible) updateButtonIcon(); } } @@ -311,8 +309,9 @@ private static void showVideoQualityDialog(Context context) { try { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); + Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); + updateButtonIcon(selectedQuality, true); menu.patch_setMenuIndexFromQuality(selectedQuality); - Logger.printDebug(() -> "Applied dialog quality: " + selectedQuality); if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { RememberVideoQualityPatch.changeDefaultQuality(selectedQuality.patch_getResolution()); From 47455bac35e41654fbf5033529eb1c8f37ccecdf Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:29:48 -0400 Subject: [PATCH 26/57] Hide the quality icon until playback starts --- .../quality/RememberVideoQualityPatch.java | 3 +- .../videoplayer/PlayerControlButton.java | 10 ++---- .../videoplayer/VideoQualityDialogButton.java | 32 +++++++++++++------ .../button/VideoQualityDialogButtonPatch.kt | 1 - ...ed_video_quality_dialog_button_unknown.xml | 9 ------ .../youtube_controls_bottom_ui_container.xml | 2 -- .../youtube_controls_bottom_ui_container.xml | 1 - 7 files changed, 26 insertions(+), 32 deletions(-) delete mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index ae9aa012a0..b88ec87e9c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -251,6 +251,7 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentQuality = null; currentMenuInterface = null; - VideoQualityDialogButton.cleanup(); + // Hide the quality button until playback starts and the qualities are available. + VideoQualityDialogButton.updateButtonIcon(null, true); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index f9eb6f9c02..cb0d2031e6 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -190,19 +190,13 @@ public void hide() { /** * Sets the icon of the button. - * @param resourceName The name of the drawable resource. + * @param resourceId Drawable identifier, or zero to hide the icon. */ - public void setIcon(String resourceName) { + public void setIcon(int resourceId) { try { View button = buttonRef.get(); if (button instanceof ImageView imageButton) { - final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); - if (resourceId == 0) { - Logger.printException(() -> "Could not set button icon to: " + resourceName); - return; - } imageButton.setImageResource(resourceId); - Logger.printDebug(() -> "Set button icon to: " + resourceName); } } catch (Exception ex) { Logger.printException(() -> "setIcon failure", ex); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index dc0ba1e023..a9cbdcb397 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -44,14 +44,26 @@ @SuppressWarnings("unused") public class VideoQualityDialogButton { + private static final int DRAWABLE_LHD = getDrawableIdentifier("revanced_video_quality_dialog_button_lhd"); + private static final int DRAWABLE_HD = getDrawableIdentifier("revanced_video_quality_dialog_button_hd"); + private static final int DRAWABLE_FHD = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd"); + private static final int DRAWABLE_QHD = getDrawableIdentifier("revanced_video_quality_dialog_button_qhd"); + private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k"); + private static final int DRAWABLE_UNKNOWN = 0; // Do not show a button icon. + + private static int getDrawableIdentifier(String resourceName) { + final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); + if (resourceId == 0) Logger.printException(() -> "Could not find resource: " + resourceName); + return resourceId; + } + @Nullable private static PlayerControlButton instance; /** * The current resource name of the button icon. */ - @Nullable - private static String currentIconResource; + private static int currentIconResource; @Nullable private static Runnable updateIconRunnable; @@ -69,16 +81,16 @@ public static void updateButtonIcon(@Nullable VideoQuality currentQuality, boole : currentQuality.patch_getResolution(); // Map quality to appropriate icon. - String iconResource = switch (resolution) { - case 144, 240, 360, 480 -> "revanced_video_quality_dialog_button_lhd"; - case 720 -> "revanced_video_quality_dialog_button_hd"; - case 1080 -> "revanced_video_quality_dialog_button_fhd"; - case 1440 -> "revanced_video_quality_dialog_button_qhd"; - case 2160 -> "revanced_video_quality_dialog_button_4k"; - default -> "revanced_video_quality_dialog_button_unknown"; + final int iconResource = switch (resolution) { + case 144, 240, 360, 480 -> DRAWABLE_LHD; + case 720 -> DRAWABLE_HD; + case 1080 -> DRAWABLE_FHD; + case 1440 -> DRAWABLE_QHD; + case 2160 -> DRAWABLE_4K; + default -> DRAWABLE_UNKNOWN; }; - if (!iconResource.equals(currentIconResource)) { + if (iconResource != currentIconResource) { cleanup(); Runnable update = () -> { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index a56c99bea0..d49502024b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -26,7 +26,6 @@ private val videoQualityButtonResourcePatch = resourcePatch { "revanced_video_quality_dialog_button_fhd.xml", "revanced_video_quality_dialog_button_qhd.xml", "revanced_video_quality_dialog_button_4k.xml", - "revanced_video_quality_dialog_button_unknown.xml", ), ) diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml deleted file mode 100644 index f4be45538b..0000000000 --- a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml index 35061848bd..deb534fbaf 100644 --- a/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/qualitybutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -13,9 +13,7 @@ android:layout_height="60.0dip" android:paddingTop="6.0dp" android:paddingBottom="0dp" - android:longClickable="false" android:scaleType="center" - android:src="@drawable/revanced_video_quality_dialog_button_unknown" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" /> diff --git a/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml index 315251b0c0..c0109ebc59 100644 --- a/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -13,7 +13,6 @@ android:layout_height="60.0dip" android:paddingTop="6.0dp" android:paddingBottom="0dp" - android:longClickable="false" android:scaleType="center" android:src="@drawable/revanced_playback_speed_dialog_button" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" From 2d0c61b832c511ea891c87b5c4447ee39fc66a92 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:34:06 -0400 Subject: [PATCH 27/57] Remove delay logic that no longer seems needed --- .../quality/RememberVideoQualityPatch.java | 6 ++-- .../videoplayer/VideoQualityDialogButton.java | 36 ++++--------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index b88ec87e9c..b3aef77a73 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -149,7 +149,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); } } @@ -163,7 +163,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); changeDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality, true); + VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } @@ -252,6 +252,6 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentMenuInterface = null; // Hide the quality button until playback starts and the qualities are available. - VideoQualityDialogButton.updateButtonIcon(null, true); + VideoQualityDialogButton.updateButtonIcon(null); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index a9cbdcb397..b66332a968 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -9,8 +9,6 @@ import android.content.res.Configuration; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; -import android.os.Handler; -import android.os.Looper; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; @@ -65,14 +63,10 @@ private static int getDrawableIdentifier(String resourceName) { */ private static int currentIconResource; - @Nullable - private static Runnable updateIconRunnable; - private static final Handler handler = new Handler(Looper.getMainLooper()); - /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon(@Nullable VideoQuality currentQuality, boolean updateImmediately) { + public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { try { if (instance == null) return; @@ -91,32 +85,14 @@ public static void updateButtonIcon(@Nullable VideoQuality currentQuality, boole }; if (iconResource != currentIconResource) { - cleanup(); - - Runnable update = () -> { - currentIconResource = iconResource; - instance.setIcon(iconResource); - }; - - if (updateImmediately) { - update.run(); - } else { - updateIconRunnable = update; - handler.postDelayed(update, 300); - } + currentIconResource = iconResource; + instance.setIcon(iconResource); } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); } } - public static void cleanup() { - if (updateIconRunnable != null) { - handler.removeCallbacks(updateIconRunnable); - updateIconRunnable = null; - } - } - /** * Injection point. */ @@ -150,7 +126,7 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality, true); + updateButtonIcon(quality); menu.patch_setMenuIndexFromQuality(quality); return true; } @@ -167,7 +143,7 @@ public static void initializeButton(View controlsView) { ); // Set initial icon. - updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality()); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -322,7 +298,7 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality, true); + updateButtonIcon(selectedQuality); menu.patch_setMenuIndexFromQuality(selectedQuality); if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { From e87485796f4901f9c0b4096f9a902b80d466940b Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:36:35 +0300 Subject: [PATCH 28/57] add 'Player buttons' submenu --- .../preference/BasePreferenceScreen.kt | 30 ++++++++++++++++++- .../preference/PreferenceScreenPreference.kt | 6 ++-- .../interaction/downloads/DownloadsPatch.kt | 27 ++++++++++------- .../overlay/HidePlayerOverlayButtonsPatch.kt | 16 ++++++---- .../button/VideoQualityDialogButtonPatch.kt | 8 ++++- .../speed/button/PlaybackSpeedButtonPatch.kt | 8 ++++- .../resources/addresources/values/strings.xml | 5 ++-- 7 files changed, 78 insertions(+), 22 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt index 8590e99658..a3d2e659cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt @@ -54,7 +54,35 @@ abstract class BasePreferenceScreen( fun addPreferences(vararg preferences: BasePreference) { ensureScreenInserted() - this.preferences.addAll(preferences) + + preferences.forEach { newPreference -> + if (newPreference is PreferenceScreenPreference) { + val existingPreference = this.preferences + .filterIsInstance() + .firstOrNull { it.originalKey == newPreference.originalKey } + + if (existingPreference != null) { + val mergedPreferences = (existingPreference.preferences + newPreference.preferences).toMutableSet() + this.preferences.remove(existingPreference) + this.preferences.add( + PreferenceScreenPreference( + key = newPreference.originalKey, + titleKey = newPreference.titleKey, + summaryKey = newPreference.summaryKey, + icon = newPreference.icon, + layout = newPreference.layout, + sorting = newPreference.sorting, + tag = newPreference.tag, + preferences = mergedPreferences + ) + ) + } else { + this.preferences.add(newPreference) + } + } else { + this.preferences.add(newPreference) + } + } } open inner class Category( diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt index a37e92947b..b87edd6909 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt @@ -19,11 +19,11 @@ import org.w3c.dom.Document @Suppress("MemberVisibilityCanBePrivate") open class PreferenceScreenPreference( key: String? = null, - titleKey: String = "${key}_title", + titleKey: String? = "${key}_title", summaryKey: String? = "${key}_summary", icon: String? = null, layout: String? = null, - sorting: Sorting = Sorting.BY_TITLE, + val sorting: Sorting = Sorting.BY_TITLE, tag: String = "PreferenceScreen", val preferences: Set, // Alternatively, instead of repurposing the key for sorting, @@ -33,6 +33,8 @@ open class PreferenceScreenPreference( // Since the key value is not currently used by the extensions, // for now it's much simpler to modify the key to include the sort parameter. ) : BasePreference(sorting.appendSortType(key), titleKey, summaryKey, icon, layout, tag) { + val originalKey: String? = key + override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit) = super.serialize(ownerDocument, resourceCallback).apply { preferences.forEach { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index 82f19f81a2..878d19a039 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -6,6 +6,7 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference @@ -34,17 +35,23 @@ private val downloadsResourcePatch = resourcePatch { PreferenceScreen.PLAYER.addPreferences( PreferenceScreenPreference( - key = "revanced_external_downloader_screen", - sorting = Sorting.UNSORTED, + key = "revanced_player_buttons_screen", preferences = setOf( - SwitchPreference("revanced_external_downloader"), - SwitchPreference("revanced_external_downloader_action_button"), - TextPreference( - "revanced_external_downloader_name", - tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference", - ), - ), - ), + PreferenceCategory( + key = "revanced_external_downloader_category", + titleKey = "revanced_external_downloader_category_title", + sorting = Sorting.UNSORTED, + preferences = mutableSetOf( + SwitchPreference("revanced_external_downloader"), + SwitchPreference("revanced_external_downloader_action_button"), + TextPreference( + key = "revanced_external_downloader_name", + tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference" + ) + ) + ) + ) + ) ) copyResources( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt index a890cf2be7..b1d7ad0970 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt @@ -12,6 +12,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen @@ -71,11 +72,16 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( addResources("youtube", "layout.buttons.overlay.hidePlayerOverlayButtonsPatch") PreferenceScreen.PLAYER.addPreferences( - SwitchPreference("revanced_hide_player_previous_next_buttons"), - SwitchPreference("revanced_hide_cast_button"), - SwitchPreference("revanced_hide_captions_button"), - SwitchPreference("revanced_hide_autoplay_button"), - SwitchPreference("revanced_hide_player_control_buttons_background"), + PreferenceScreenPreference( + key = "revanced_player_buttons_screen", + preferences = setOf( + SwitchPreference("revanced_hide_player_previous_next_buttons"), + SwitchPreference("revanced_hide_cast_button"), + SwitchPreference("revanced_hide_captions_button"), + SwitchPreference("revanced_hide_autoplay_button"), + SwitchPreference("revanced_hide_player_control_buttons_background"), + ), + ), ) // region Hide player next/previous button. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index a56c99bea0..b01f369ada 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playercontrols.* @@ -53,7 +54,12 @@ val videoQualityButtonPatch = bytecodePatch( addResources("youtube", "video.quality.button.videoQualityButtonPatch") PreferenceScreen.PLAYER.addPreferences( - SwitchPreference("revanced_video_quality_dialog_button"), + PreferenceScreenPreference( + key = "revanced_player_buttons_screen", + preferences = setOf( + SwitchPreference("revanced_video_quality_dialog_button"), + ), + ), ) initializeBottomControl(QUALITY_BUTTON_CLASS_DESCRIPTOR) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt index 0ac9b0769e..133a0ec405 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playercontrols.* @@ -48,7 +49,12 @@ val playbackSpeedButtonPatch = bytecodePatch( addResources("youtube", "video.speed.button.playbackSpeedButtonPatch") PreferenceScreen.PLAYER.addPreferences( - SwitchPreference("revanced_playback_speed_dialog_button"), + PreferenceScreenPreference( + key = "revanced_player_buttons_screen", + preferences = setOf( + SwitchPreference("revanced_playback_speed_dialog_button"), + ), + ), ) initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 80baa19af9..d6495207e7 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -161,6 +161,8 @@ Tap the continue button and allow optimization changes." Show settings search history Settings search history is shown Settings search history is not shown + Player buttons + Hide or show player buttons Disable Shorts background play @@ -521,8 +523,7 @@ This feature is only available for older devices" Double tap can occasionally trigger a skip to the next/previous chapter - External downloads - Settings for using an external downloader + External downloads Show external download button Download button in player is shown Download button in player is not shown From c008b291232dfa0e5f97e45ea68cb5a819738d61 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:41:49 -0400 Subject: [PATCH 29/57] Adjust strings --- patches/api/patches.api | 2 ++ patches/src/main/resources/addresources/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/patches/api/patches.api b/patches/api/patches.api index 91f78e8db7..2e70a57991 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -861,7 +861,9 @@ public class app/revanced/patches/shared/misc/settings/preference/PreferenceCate public class app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference : app/revanced/patches/shared/misc/settings/preference/BasePreference { public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting;Ljava/lang/String;Ljava/util/Set;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting;Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getOriginalKey ()Ljava/lang/String; public final fun getPreferences ()Ljava/util/Set; + public final fun getSorting ()Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting; public fun serialize (Lorg/w3c/dom/Document;Lkotlin/jvm/functions/Function1;)Lorg/w3c/dom/Element; } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index d6495207e7..9dfeaeb732 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1546,7 +1546,7 @@ Enabling this can unlock higher video qualities" Show video quality button - Button is shown. Tap and hold to reset video quality to Auto + Button is shown. Tap and hold to reset quality to default Button is not shown From 35dbf51288db173d5c7ac25cfa6d4ff0d77b7a3c Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:51:54 -0400 Subject: [PATCH 30/57] Use haptic feedback on long press to open dialog --- .../youtube/videoplayer/VideoQualityDialogButton.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index b66332a968..ceca7eb934 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -135,6 +135,7 @@ public static void initializeButton(View controlsView) { // Existing hook cannot set default quality to auto. // Instead show the quality dialog. showVideoQualityDialog(view.getContext()); + return true; } catch (Exception ex) { Logger.printException(() -> "Video quality button reset failure", ex); } @@ -180,7 +181,7 @@ private static void showVideoQualityDialog(Context context) { } if (currentQualities.size() < 2) { // Should never happen. - Logger.printDebug(() -> "Cannot show qualities dialog, no qualities available"); + Logger.printException(() -> "Cannot show qualities dialog, no qualities available"); return; } From 29ad1f8f8c847558955f94491aa3a9dddc61b373 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:00:29 -0400 Subject: [PATCH 31/57] Revert "add 'Player buttons' submenu" This reverts commit e87485796f4901f9c0b4096f9a902b80d466940b. --- patches/api/patches.api | 2 -- .../preference/BasePreferenceScreen.kt | 30 +------------------ .../preference/PreferenceScreenPreference.kt | 6 ++-- .../interaction/downloads/DownloadsPatch.kt | 27 +++++++---------- .../overlay/HidePlayerOverlayButtonsPatch.kt | 16 ++++------ .../button/VideoQualityDialogButtonPatch.kt | 8 +---- .../speed/button/PlaybackSpeedButtonPatch.kt | 8 +---- .../resources/addresources/values/strings.xml | 5 ++-- 8 files changed, 22 insertions(+), 80 deletions(-) diff --git a/patches/api/patches.api b/patches/api/patches.api index 2e70a57991..91f78e8db7 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -861,9 +861,7 @@ public class app/revanced/patches/shared/misc/settings/preference/PreferenceCate public class app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference : app/revanced/patches/shared/misc/settings/preference/BasePreference { public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting;Ljava/lang/String;Ljava/util/Set;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting;Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getOriginalKey ()Ljava/lang/String; public final fun getPreferences ()Ljava/util/Set; - public final fun getSorting ()Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference$Sorting; public fun serialize (Lorg/w3c/dom/Document;Lkotlin/jvm/functions/Function1;)Lorg/w3c/dom/Element; } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt index a3d2e659cf..8590e99658 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt @@ -54,35 +54,7 @@ abstract class BasePreferenceScreen( fun addPreferences(vararg preferences: BasePreference) { ensureScreenInserted() - - preferences.forEach { newPreference -> - if (newPreference is PreferenceScreenPreference) { - val existingPreference = this.preferences - .filterIsInstance() - .firstOrNull { it.originalKey == newPreference.originalKey } - - if (existingPreference != null) { - val mergedPreferences = (existingPreference.preferences + newPreference.preferences).toMutableSet() - this.preferences.remove(existingPreference) - this.preferences.add( - PreferenceScreenPreference( - key = newPreference.originalKey, - titleKey = newPreference.titleKey, - summaryKey = newPreference.summaryKey, - icon = newPreference.icon, - layout = newPreference.layout, - sorting = newPreference.sorting, - tag = newPreference.tag, - preferences = mergedPreferences - ) - ) - } else { - this.preferences.add(newPreference) - } - } else { - this.preferences.add(newPreference) - } - } + this.preferences.addAll(preferences) } open inner class Category( diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt index b87edd6909..a37e92947b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt @@ -19,11 +19,11 @@ import org.w3c.dom.Document @Suppress("MemberVisibilityCanBePrivate") open class PreferenceScreenPreference( key: String? = null, - titleKey: String? = "${key}_title", + titleKey: String = "${key}_title", summaryKey: String? = "${key}_summary", icon: String? = null, layout: String? = null, - val sorting: Sorting = Sorting.BY_TITLE, + sorting: Sorting = Sorting.BY_TITLE, tag: String = "PreferenceScreen", val preferences: Set, // Alternatively, instead of repurposing the key for sorting, @@ -33,8 +33,6 @@ open class PreferenceScreenPreference( // Since the key value is not currently used by the extensions, // for now it's much simpler to modify the key to include the sort parameter. ) : BasePreference(sorting.appendSortType(key), titleKey, summaryKey, icon, layout, tag) { - val originalKey: String? = key - override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit) = super.serialize(ownerDocument, resourceCallback).apply { preferences.forEach { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt index 878d19a039..82f19f81a2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference @@ -35,23 +34,17 @@ private val downloadsResourcePatch = resourcePatch { PreferenceScreen.PLAYER.addPreferences( PreferenceScreenPreference( - key = "revanced_player_buttons_screen", + key = "revanced_external_downloader_screen", + sorting = Sorting.UNSORTED, preferences = setOf( - PreferenceCategory( - key = "revanced_external_downloader_category", - titleKey = "revanced_external_downloader_category_title", - sorting = Sorting.UNSORTED, - preferences = mutableSetOf( - SwitchPreference("revanced_external_downloader"), - SwitchPreference("revanced_external_downloader_action_button"), - TextPreference( - key = "revanced_external_downloader_name", - tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference" - ) - ) - ) - ) - ) + SwitchPreference("revanced_external_downloader"), + SwitchPreference("revanced_external_downloader_action_button"), + TextPreference( + "revanced_external_downloader_name", + tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference", + ), + ), + ), ) copyResources( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt index b1d7ad0970..a890cf2be7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/overlay/HidePlayerOverlayButtonsPatch.kt @@ -12,7 +12,6 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen @@ -72,16 +71,11 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch( addResources("youtube", "layout.buttons.overlay.hidePlayerOverlayButtonsPatch") PreferenceScreen.PLAYER.addPreferences( - PreferenceScreenPreference( - key = "revanced_player_buttons_screen", - preferences = setOf( - SwitchPreference("revanced_hide_player_previous_next_buttons"), - SwitchPreference("revanced_hide_cast_button"), - SwitchPreference("revanced_hide_captions_button"), - SwitchPreference("revanced_hide_autoplay_button"), - SwitchPreference("revanced_hide_player_control_buttons_background"), - ), - ), + SwitchPreference("revanced_hide_player_previous_next_buttons"), + SwitchPreference("revanced_hide_cast_button"), + SwitchPreference("revanced_hide_captions_button"), + SwitchPreference("revanced_hide_autoplay_button"), + SwitchPreference("revanced_hide_player_control_buttons_background"), ) // region Hide player next/previous button. diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index 59f03e5d41..d49502024b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -4,7 +4,6 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playercontrols.* @@ -53,12 +52,7 @@ val videoQualityButtonPatch = bytecodePatch( addResources("youtube", "video.quality.button.videoQualityButtonPatch") PreferenceScreen.PLAYER.addPreferences( - PreferenceScreenPreference( - key = "revanced_player_buttons_screen", - preferences = setOf( - SwitchPreference("revanced_video_quality_dialog_button"), - ), - ), + SwitchPreference("revanced_video_quality_dialog_button"), ) initializeBottomControl(QUALITY_BUTTON_CLASS_DESCRIPTOR) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt index 133a0ec405..0ac9b0769e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/button/PlaybackSpeedButtonPatch.kt @@ -4,7 +4,6 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playercontrols.* @@ -49,12 +48,7 @@ val playbackSpeedButtonPatch = bytecodePatch( addResources("youtube", "video.speed.button.playbackSpeedButtonPatch") PreferenceScreen.PLAYER.addPreferences( - PreferenceScreenPreference( - key = "revanced_player_buttons_screen", - preferences = setOf( - SwitchPreference("revanced_playback_speed_dialog_button"), - ), - ), + SwitchPreference("revanced_playback_speed_dialog_button"), ) initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 9dfeaeb732..3624d7f226 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -161,8 +161,6 @@ Tap the continue button and allow optimization changes." Show settings search history Settings search history is shown Settings search history is not shown - Player buttons - Hide or show player buttons Disable Shorts background play @@ -523,7 +521,8 @@ This feature is only available for older devices" Double tap can occasionally trigger a skip to the next/previous chapter - External downloads + External downloads + Settings for using an external downloader Show external download button Download button in player is shown Download button in player is not shown From 563321a32f3ea46d0846cd5a6afbd24bc61e3b58 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:05:40 -0400 Subject: [PATCH 32/57] refactor --- .../java/app/revanced/extension/shared/Utils.java | 2 +- .../youtube/videoplayer/VideoQualityDialogButton.java | 9 ++++----- .../video/quality/RememberVideoQualityPatch.kt | 11 +++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index 173de404a9..c015c06105 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -1461,7 +1461,7 @@ public static int percentageWidthToPixels(int percentage) { } /** - * Uses {@link #adjustColorBrightness(int, float)} depending if dark mode is enabled or not. + * Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active. */ @ColorInt public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index ceca7eb934..dd4d08933a 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -3,6 +3,7 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.dipToPixels; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE; +import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VideoQualityMenuInterface; import android.app.Dialog; import android.content.Context; @@ -113,8 +114,7 @@ public static void initializeButton(View controlsView) { view -> { try { List qualities = RememberVideoQualityPatch.getCurrentQualities(); - RememberVideoQualityPatch.VideoQualityMenuInterface menu - = RememberVideoQualityPatch.getCurrentMenuInterface(); + VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); if (qualities == null || menu == null) { Logger.printDebug(() -> "Cannot reset quality, videoQualities is null"); return true; @@ -185,7 +185,7 @@ private static void showVideoQualityDialog(Context context) { return; } - RememberVideoQualityPatch.VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); + VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface(); if (menu == null) { Logger.printDebug(() -> "Cannot show qualities dialog, menu is null"); return; @@ -194,8 +194,7 @@ private static void showVideoQualityDialog(Context context) { // -1 adjustment for automatic quality at first index. final int listViewSelectedIndex = currentQualities.indexOf(currentQuality) - 1; - final int qualitySize = currentQualities.size(); - List qualityLabels = new ArrayList<>(qualitySize - 1); + List qualityLabels = new ArrayList<>(currentQualities.size() - 1); for (VideoQuality availableQuality : currentQualities) { if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { qualityLabels.add(availableQuality.patch_getQualityName()); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index 1e42048ba6..87a0c233fb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -82,7 +82,7 @@ val rememberVideoQualityPatch = bytecodePatch { 0, """ invoke-static { p2, p1 }, $EXTENSION_CLASS_DESCRIPTOR->fixVideoQualityResolution(Ljava/lang/String;I)I - move-result p1 + move-result p1 """ ) @@ -160,11 +160,6 @@ val rememberVideoQualityPatch = bytecodePatch { // Add interface and helper methods to allow extension code to call obfuscated methods. interfaces.add(EXTENSION_VIDEO_QUALITY_MENU_INTERFACE) - // Get the name of the setQualityByIndex method. - val setQualityMenuIndexMethod = methods.single { - method -> method.parameterTypes.firstOrNull() == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE - } - methods.add( ImmutableMethod( type, @@ -178,6 +173,10 @@ val rememberVideoQualityPatch = bytecodePatch { null, MutableMethodImplementation(2), ).toMutable().apply { + val setQualityMenuIndexMethod = methods.single { method -> + method.parameterTypes.firstOrNull() == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE + } + addInstructions( 0, """ From da06d7a69d268db2d171cf1d6cf7c800c09aa4bd Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:14:40 -0400 Subject: [PATCH 33/57] Always show current resolution in flyoug and do not show "automatic" --- .../playback/quality/RememberVideoQualityPatch.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index b3aef77a73..2374070323 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -56,7 +56,7 @@ public interface VideoQualityMenuInterface { private static List currentQualities; /** - * The current quality of the video playing. + * The current quality of the video playing. This can never be the automatic value. */ @Nullable private static VideoQuality currentQuality; @@ -143,14 +143,13 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte } VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; - if (currentQuality == null - || currentQuality.patch_getResolution() != updatedCurrentQuality.patch_getResolution()) { + if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE && + (currentQuality == null + || currentQuality.patch_getResolution() != updatedCurrentQuality.patch_getResolution())) { currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) { - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); - } + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); } final int preferredQuality = getDefaultQualityResolution(); From 9cb5badeff9e7a68e68da9d99ca6140b4abd42c2 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 2 Aug 2025 10:09:54 +0300 Subject: [PATCH 34/57] add 'LD', 'SD', 'FHD_PLUS' icons --- .../youtube/videoplayer/VideoQualityDialogButton.java | 8 ++++++-- .../quality/button/VideoQualityDialogButtonPatch.kt | 4 +++- .../revanced_video_quality_dialog_button_fhd_plus.xml | 9 +++++++++ ...d.xml => revanced_video_quality_dialog_button_ld.xml} | 2 +- .../drawable/revanced_video_quality_dialog_button_sd.xml | 9 +++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml rename patches/src/main/resources/qualitybutton/drawable/{revanced_video_quality_dialog_button_lhd.xml => revanced_video_quality_dialog_button_ld.xml} (52%) create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_sd.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index dd4d08933a..edb7c80005 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -43,9 +43,11 @@ @SuppressWarnings("unused") public class VideoQualityDialogButton { - private static final int DRAWABLE_LHD = getDrawableIdentifier("revanced_video_quality_dialog_button_lhd"); + private static final int DRAWABLE_LD = getDrawableIdentifier("revanced_video_quality_dialog_button_ld"); + private static final int DRAWABLE_SD = getDrawableIdentifier("revanced_video_quality_dialog_button_sd"); private static final int DRAWABLE_HD = getDrawableIdentifier("revanced_video_quality_dialog_button_hd"); private static final int DRAWABLE_FHD = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd"); + private static final int DRAWABLE_FHD_PLUS = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd_plus"); private static final int DRAWABLE_QHD = getDrawableIdentifier("revanced_video_quality_dialog_button_qhd"); private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k"); private static final int DRAWABLE_UNKNOWN = 0; // Do not show a button icon. @@ -77,9 +79,11 @@ public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { // Map quality to appropriate icon. final int iconResource = switch (resolution) { - case 144, 240, 360, 480 -> DRAWABLE_LHD; + case 144, 240, 360 -> DRAWABLE_LD; + case 480 -> DRAWABLE_SD; case 720 -> DRAWABLE_HD; case 1080 -> DRAWABLE_FHD; + // case 1080+ -> DRAWABLE_FHD_PLUS; // TODO: Change to real parameter. case 1440 -> DRAWABLE_QHD; case 2160 -> DRAWABLE_4K; default -> DRAWABLE_UNKNOWN; diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index d49502024b..ffaed67ad5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -21,9 +21,11 @@ private val videoQualityButtonResourcePatch = resourcePatch { "qualitybutton", ResourceGroup( "drawable", - "revanced_video_quality_dialog_button_lhd.xml", + "revanced_video_quality_dialog_button_ld.xml", + "revanced_video_quality_dialog_button_sd.xml", "revanced_video_quality_dialog_button_hd.xml", "revanced_video_quality_dialog_button_fhd.xml", + "revanced_video_quality_dialog_button_fhd_plus.xml", "revanced_video_quality_dialog_button_qhd.xml", "revanced_video_quality_dialog_button_4k.xml", ), diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml new file mode 100644 index 0000000000..968f40119c --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_ld.xml similarity index 52% rename from patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml rename to patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_ld.xml index 400a95044d..a49f5a200c 100644 --- a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_lhd.xml +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_ld.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M3.61719,5 C3.15625,5,2.76953,5.15234,2.46094,5.46094 C2.15234,5.76953,2,6.15625,2,6.61719 L2,17.3828 C2,17.8438,2.15234,18.2305,2.46094,18.5391 C2.76953,18.8477,3.15625,19,3.61719,19 L20.3828,19 C20.8438,19,21.2305,18.8477,21.5391,18.5391 C21.8477,18.2305,22,17.8438,22,17.3828 L22,6.61719 C22,6.15625,21.8477,5.76953,21.5391,5.46094 C21.2305,5.15234,20.8438,5,20.3828,5 L3.61719,5 Z M3.61719,6 L20.3828,6 C20.5625,6,20.7109,6.05859,20.8281,6.17188 C20.9414,6.28906,21,6.4375,21,6.61719 L21,17.3828 C21,17.5625,20.9414,17.7109,20.8281,17.8281 C20.7109,17.9414,20.5625,18,20.3828,18 L3.61719,18 C3.4375,18,3.28906,17.9414,3.17188,17.8281 C3.05859,17.7109,3,17.5625,3,17.3828 L3,6.61719 C3,6.4375,3.05859,6.28906,3.17188,6.17188 C3.28906,6.05859,3.4375,6,3.61719,6 Z M6.61523,9.30859 L6.61523,14.6914 L10.998,14.6914 L10.998,13.8086 L7.49805,13.8086 L7.49805,9.30859 L6.61523,9.30859 Z M13,9.30859 L13,14.6914 L16.3086,14.6914 C16.6048,14.6914,16.8575,14.5878,17.0684,14.377 C17.2792,14.166,17.3848,13.9112,17.3848,13.6152 L17.3848,10.3848 C17.3848,10.0888,17.2792,9.83405,17.0684,9.62305 C16.8575,9.41221,16.6048,9.30859,16.3086,9.30859 L13,9.30859 Z M13.8848,10.1914 L16.1914,10.1914 C16.2684,10.1914,16.3403,10.2249,16.4043,10.2891 C16.4685,10.3531,16.5,10.423,16.5,10.5 L16.5,13.5 C16.5,13.577,16.4685,13.6469,16.4043,13.7109 C16.3403,13.7751,16.2684,13.8086,16.1914,13.8086 L13.8848,13.8086 L13.8848,10.1914 Z" /> diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_sd.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_sd.xml new file mode 100644 index 0000000000..150ede8502 --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_sd.xml @@ -0,0 +1,9 @@ + + + From 8f6bf5c03498da7487c8a0a08a9e16b0bc39396f Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:38:48 +0300 Subject: [PATCH 35/57] add a shimmer effect to unknown icon state --- .../videoplayer/PlayerControlButton.java | 37 +++++++++++++++++++ .../videoplayer/VideoQualityDialogButton.java | 14 ++++++- .../button/VideoQualityDialogButtonPatch.kt | 5 +++ ...ed_video_quality_dialog_button_shimmer.xml | 10 +++++ ...ed_video_quality_dialog_button_unknown.xml | 11 ++++++ 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml create mode 100644 patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index cb0d2031e6..94c92b738c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -202,4 +202,41 @@ public void setIcon(int resourceId) { Logger.printException(() -> "setIcon failure", ex); } } + + /** + * Starts an animation on the button. + * @param animation The animation to apply. + */ + public void startAnimation(Animation animation) { + try { + View button = buttonRef.get(); + if (button != null) { + button.startAnimation(animation); + } + } catch (Exception ex) { + Logger.printException(() -> "startAnimation failure", ex); + } + } + + /** + * Clears any animation on the button. + */ + public void clearAnimation() { + try { + View button = buttonRef.get(); + if (button != null) { + button.clearAnimation(); + } + } catch (Exception ex) { + Logger.printException(() -> "clearAnimation failure", ex); + } + } + + /** + * Returns the View associated with this button. + * @return The button View. + */ + public View getView() { + return buttonRef.get(); + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index edb7c80005..1988f00081 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -21,6 +21,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.view.animation.TranslateAnimation; import android.widget.ArrayAdapter; import android.widget.ImageView; @@ -50,7 +51,7 @@ public class VideoQualityDialogButton { private static final int DRAWABLE_FHD_PLUS = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd_plus"); private static final int DRAWABLE_QHD = getDrawableIdentifier("revanced_video_quality_dialog_button_qhd"); private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k"); - private static final int DRAWABLE_UNKNOWN = 0; // Do not show a button icon. + private static final int DRAWABLE_UNKNOWN = getDrawableIdentifier("revanced_video_quality_dialog_button_unknown"); private static int getDrawableIdentifier(String resourceName) { final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); @@ -91,7 +92,16 @@ public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { if (iconResource != currentIconResource) { currentIconResource = iconResource; - instance.setIcon(iconResource); + if (iconResource == DRAWABLE_UNKNOWN) { + instance.setIcon(iconResource); + // Start shimmer animation for unknown state. + Animation shimmer = AnimationUtils.loadAnimation(instance.getView().getContext(), + Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); + instance.startAnimation(shimmer); + } else { + instance.clearAnimation(); // Clear animation for known states. + instance.setIcon(iconResource); + } } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index ffaed67ad5..18a7f258c2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -19,6 +19,10 @@ private val videoQualityButtonResourcePatch = resourcePatch { execute { copyResources( "qualitybutton", + ResourceGroup( + "anim", + "revanced_video_quality_dialog_button_shimmer.xml", + ), ResourceGroup( "drawable", "revanced_video_quality_dialog_button_ld.xml", @@ -28,6 +32,7 @@ private val videoQualityButtonResourcePatch = resourcePatch { "revanced_video_quality_dialog_button_fhd_plus.xml", "revanced_video_quality_dialog_button_qhd.xml", "revanced_video_quality_dialog_button_4k.xml", + "revanced_video_quality_dialog_button_unknown.xml", ), ) diff --git a/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml b/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml new file mode 100644 index 0000000000..e51e54ee3d --- /dev/null +++ b/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml @@ -0,0 +1,10 @@ + + + + diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml new file mode 100644 index 0000000000..4e5750cfe3 --- /dev/null +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_unknown.xml @@ -0,0 +1,11 @@ + + + From 1c24b39fde8ae1d488806a7cd551ffe88dca7ae1 Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 2 Aug 2025 14:21:47 +0300 Subject: [PATCH 36/57] refactor -> add a fade animations --- .../videoplayer/PlayerControlButton.java | 4 +- .../videoplayer/VideoQualityDialogButton.java | 39 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index 94c92b738c..86622406ce 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -24,8 +24,8 @@ public interface PlayerControlButtonVisibility { private static final int fadeInDuration; private static final int fadeOutDuration; - private static final Animation fadeInAnimation; - private static final Animation fadeOutAnimation; + static final Animation fadeInAnimation; + static final Animation fadeOutAnimation; private static final Animation fadeOutImmediate; static { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 1988f00081..d7b3f7d794 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -67,8 +67,32 @@ private static int getDrawableIdentifier(String resourceName) { */ private static int currentIconResource; + private static final Animation shimmerAnimation; + private static final Animation.AnimationListener iconChangeListener = new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + instance.setIcon(currentIconResource); + instance.startAnimation(currentIconResource == DRAWABLE_UNKNOWN + ? shimmerAnimation + : PlayerControlButton.fadeInAnimation); + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }; + + static { + shimmerAnimation = AnimationUtils.loadAnimation( + Utils.getContext(), + Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim") + ); + } + /** - * Updates the button icon based on the current video quality. + * Updates the button icon based on the current video quality with a fade animation. */ public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { try { @@ -92,16 +116,9 @@ public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { if (iconResource != currentIconResource) { currentIconResource = iconResource; - if (iconResource == DRAWABLE_UNKNOWN) { - instance.setIcon(iconResource); - // Start shimmer animation for unknown state. - Animation shimmer = AnimationUtils.loadAnimation(instance.getView().getContext(), - Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); - instance.startAnimation(shimmer); - } else { - instance.clearAnimation(); // Clear animation for known states. - instance.setIcon(iconResource); - } + instance.clearAnimation(); + PlayerControlButton.fadeOutAnimation.setAnimationListener(iconChangeListener); + instance.startAnimation(PlayerControlButton.fadeOutAnimation); } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); From 4551046ce009b75ca62234b56138b70673d3260a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 07:23:38 -0400 Subject: [PATCH 37/57] Use enhanced bitrate icon --- .../quality/RememberVideoQualityPatch.java | 13 +++++++++--- .../videoplayer/VideoQualityDialogButton.java | 21 +++++++++++-------- .../quality/RememberVideoQualityPatch.kt | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 2374070323..53795d969f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -26,10 +26,17 @@ public class RememberVideoQualityPatch { * Interface to use obfuscated methods. */ public interface VideoQualityMenuInterface { - void patch_setMenuIndexFromQuality(VideoQuality quality); + void patch_setQuality(VideoQuality quality); } public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; + + /** + * All video names are the same for all languages, including enhanced bitrate. + * VideoQuality also has a resolution enum that can be used if needed. + */ + public static final String VIDEO_QUALITY_1080P_ENHANCED = "1080p Premium"; + private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI; @@ -145,7 +152,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality updatedCurrentQuality = qualities[originalQualityIndex]; if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE && (currentQuality == null - || currentQuality.patch_getResolution() != updatedCurrentQuality.patch_getResolution())) { + || !currentQuality.patch_getQualityName().equals(updatedCurrentQuality.patch_getQualityName()))) { currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); @@ -193,7 +200,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte // To prevent user confusion, set the video index even if the // quality is already correct so the UI picker will not display "Auto". Logger.printDebug(() -> "Changing quality to default: " + quality); - menu.patch_setMenuIndexFromQuality(qualities[i]); + menu.patch_setQuality(qualities[i]); return i; } i++; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index d7b3f7d794..93c8b20ad2 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -3,6 +3,7 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.dipToPixels; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE; +import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VIDEO_QUALITY_1080P_ENHANCED; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VideoQualityMenuInterface; import android.app.Dialog; @@ -94,21 +95,22 @@ public void onAnimationRepeat(Animation animation) {} /** * Updates the button icon based on the current video quality with a fade animation. */ - public static void updateButtonIcon(@Nullable VideoQuality currentQuality) { + public static void updateButtonIcon(@Nullable VideoQuality quality) { try { if (instance == null) return; - final int resolution = currentQuality == null + final int resolution = quality == null ? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading. - : currentQuality.patch_getResolution(); + : quality.patch_getResolution(); // Map quality to appropriate icon. final int iconResource = switch (resolution) { case 144, 240, 360 -> DRAWABLE_LD; case 480 -> DRAWABLE_SD; case 720 -> DRAWABLE_HD; - case 1080 -> DRAWABLE_FHD; - // case 1080+ -> DRAWABLE_FHD_PLUS; // TODO: Change to real parameter. + case 1080 -> VIDEO_QUALITY_1080P_ENHANCED.equals(quality.patch_getQualityName()) + ? DRAWABLE_FHD_PLUS + : DRAWABLE_FHD; case 1440 -> DRAWABLE_QHD; case 2160 -> DRAWABLE_4K; default -> DRAWABLE_UNKNOWN; @@ -158,7 +160,7 @@ public static void initializeButton(View controlsView) { if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); updateButtonIcon(quality); - menu.patch_setMenuIndexFromQuality(quality); + menu.patch_setQuality(quality); return true; } } @@ -286,7 +288,8 @@ private static void showVideoQualityDialog(Context context) { // Append separator part with adjusted title color. int separatorStart = spannableTitle.length(); spannableTitle.append(separatorPart); - final int adjustedTitleForegroundColor = Utils.adjustColorBrightness(Utils.getAppForegroundColor(), 1.6f, 0.6f); + final int adjustedTitleForegroundColor = Utils.adjustColorBrightness( + Utils.getAppForegroundColor(), 1.6f, 0.6f); spannableTitle.setSpan( new ForegroundColorSpan(adjustedTitleForegroundColor), separatorStart, @@ -330,7 +333,7 @@ private static void showVideoQualityDialog(Context context) { VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); updateButtonIcon(selectedQuality); - menu.patch_setMenuIndexFromQuality(selectedQuality); + menu.patch_setQuality(selectedQuality); if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { RememberVideoQualityPatch.changeDefaultQuality(selectedQuality.patch_getResolution()); @@ -443,7 +446,7 @@ public CustomQualityAdapter(@NonNull Context context, @NonNull List obje super(context, 0, objects); } - public void setSelectedPosition(int position) { + private void setSelectedPosition(int position) { this.selectedPosition = position; notifyDataSetChanged(); } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index 87a0c233fb..b5ecce54ba 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -163,7 +163,7 @@ val rememberVideoQualityPatch = bytecodePatch { methods.add( ImmutableMethod( type, - "patch_setMenuIndexFromQuality", + "patch_setQuality", listOf( ImmutableMethodParameter(YOUTUBE_VIDEO_QUALITY_CLASS_TYPE, null, null) ), From 4330299a3ed55c67e8febeeef1543332122b310a Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 2 Aug 2025 14:30:59 +0300 Subject: [PATCH 38/57] Revert "refactor -> add a fade animations" This reverts commit 1c24b39fde8ae1d488806a7cd551ffe88dca7ae1. --- .../videoplayer/PlayerControlButton.java | 4 +- .../videoplayer/VideoQualityDialogButton.java | 39 ++++++------------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java index 86622406ce..94c92b738c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -24,8 +24,8 @@ public interface PlayerControlButtonVisibility { private static final int fadeInDuration; private static final int fadeOutDuration; - static final Animation fadeInAnimation; - static final Animation fadeOutAnimation; + private static final Animation fadeInAnimation; + private static final Animation fadeOutAnimation; private static final Animation fadeOutImmediate; static { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 93c8b20ad2..1dff909ba8 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -68,32 +68,8 @@ private static int getDrawableIdentifier(String resourceName) { */ private static int currentIconResource; - private static final Animation shimmerAnimation; - private static final Animation.AnimationListener iconChangeListener = new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) {} - - @Override - public void onAnimationEnd(Animation animation) { - instance.setIcon(currentIconResource); - instance.startAnimation(currentIconResource == DRAWABLE_UNKNOWN - ? shimmerAnimation - : PlayerControlButton.fadeInAnimation); - } - - @Override - public void onAnimationRepeat(Animation animation) {} - }; - - static { - shimmerAnimation = AnimationUtils.loadAnimation( - Utils.getContext(), - Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim") - ); - } - /** - * Updates the button icon based on the current video quality with a fade animation. + * Updates the button icon based on the current video quality. */ public static void updateButtonIcon(@Nullable VideoQuality quality) { try { @@ -118,9 +94,16 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { if (iconResource != currentIconResource) { currentIconResource = iconResource; - instance.clearAnimation(); - PlayerControlButton.fadeOutAnimation.setAnimationListener(iconChangeListener); - instance.startAnimation(PlayerControlButton.fadeOutAnimation); + if (iconResource == DRAWABLE_UNKNOWN) { + instance.setIcon(iconResource); + // Start shimmer animation for unknown state. + Animation shimmer = AnimationUtils.loadAnimation(instance.getView().getContext(), + Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); + instance.startAnimation(shimmer); + } else { + instance.clearAnimation(); // Clear animation for known states. + instance.setIcon(iconResource); + } } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); From 802f809b69d1a7425b5aa43fbe843b504e515a33 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 07:55:02 -0400 Subject: [PATCH 39/57] fix 1080p Premium used when 1080p was requested --- .../extension/youtube/videoplayer/VideoQualityDialogButton.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 1dff909ba8..63eeb3fd1c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -316,6 +316,8 @@ private static void showVideoQualityDialog(Context context) { VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); updateButtonIcon(selectedQuality); + // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. + RememberVideoQualityPatch.userChangedQuality(originalIndex); menu.patch_setQuality(selectedQuality); if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { From 7cbfe8da6de2250d0a21934065b330663eebfe55 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 08:00:34 -0400 Subject: [PATCH 40/57] refactor --- .../quality/RememberVideoQualityPatch.java | 40 +++++++++---------- .../videoplayer/VideoQualityDialogButton.java | 7 +--- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 53795d969f..d2ba2217ea 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -74,21 +74,6 @@ public interface VideoQualityMenuInterface { @Nullable private static VideoQualityMenuInterface currentMenuInterface; - public static boolean shouldRememberVideoQuality() { - BooleanSetting preference = ShortsPlayerState.isOpen() ? - Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED - : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; - return preference.get(); - } - - public static int getDefaultQualityResolution() { - final boolean isShorts = ShortsPlayerState.isOpen(); - IntegerSetting preference = Utils.getNetworkType() == NetworkType.MOBILE - ? (isShorts ? shortsQualityMobile : videoQualityMobile) - : (isShorts ? shortsQualityWifi : videoQualityWifi); - return preference.get(); - } - @Nullable public static List getCurrentQualities() { return currentQualities; @@ -104,7 +89,22 @@ public static VideoQualityMenuInterface getCurrentMenuInterface() { return currentMenuInterface; } - public static void changeDefaultQuality(int qualityResolution) { + private static boolean shouldRememberVideoQuality() { + BooleanSetting preference = ShortsPlayerState.isOpen() ? + Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED + : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; + return preference.get(); + } + + public static int getDefaultQualityResolution() { + final boolean isShorts = ShortsPlayerState.isOpen(); + IntegerSetting preference = Utils.getNetworkType() == NetworkType.MOBILE + ? (isShorts ? shortsQualityMobile : videoQualityMobile) + : (isShorts ? shortsQualityWifi : videoQualityWifi); + return preference.get(); + } + + private static void rememberDefaultQuality(int qualityResolution) { final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; IntegerSetting qualitySetting; @@ -168,7 +168,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte userChangedDefaultQuality = false; VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); - changeDefaultQuality(quality.patch_getResolution()); + rememberDefaultQuality(quality.patch_getResolution()); VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } @@ -240,9 +240,9 @@ public static void userChangedQuality(int qualityIndex) { */ public static void userChangedQualityInFlyout(int videoResolution) { Utils.verifyOnMainThread(); - if (!shouldRememberVideoQuality()) return; - - changeDefaultQuality(videoResolution); + if (shouldRememberVideoQuality()) { + rememberDefaultQuality(videoResolution); + } } /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 63eeb3fd1c..0be824dc1d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -317,13 +317,10 @@ private static void showVideoQualityDialog(Context context) { Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); updateButtonIcon(selectedQuality); // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. + // Method also handles saving default quality if needed. RememberVideoQualityPatch.userChangedQuality(originalIndex); menu.patch_setQuality(selectedQuality); - - if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { - RememberVideoQualityPatch.changeDefaultQuality(selectedQuality.patch_getResolution()); - } - + dialog.dismiss(); } catch (Exception ex) { Logger.printException(() -> "Video quality selection failure", ex); From e9a24d6d42c140a3bd64c170cadc8be991d2d69f Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 08:03:29 -0400 Subject: [PATCH 41/57] Add delay to resolve flicking on rapid quality changes --- .../quality/RememberVideoQualityPatch.java | 6 +- .../videoplayer/VideoQualityDialogButton.java | 64 +++++++++++++------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index d2ba2217ea..516dd82e2d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,7 +156,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); } final int preferredQuality = getDefaultQualityResolution(); @@ -169,7 +169,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); rememberDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality); + VideoQualityDialogButton.updateButtonIcon(quality, true); return userSelectedQualityIndex; } @@ -258,6 +258,6 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentMenuInterface = null; // Hide the quality button until playback starts and the qualities are available. - VideoQualityDialogButton.updateButtonIcon(null); + VideoQualityDialogButton.updateButtonIcon(null, true); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 0be824dc1d..d7508e4297 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -11,6 +11,8 @@ import android.content.res.Configuration; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; +import android.os.Handler; +import android.os.Looper; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; @@ -45,6 +47,7 @@ @SuppressWarnings("unused") public class VideoQualityDialogButton { + private static final int DRAWABLE_LD = getDrawableIdentifier("revanced_video_quality_dialog_button_ld"); private static final int DRAWABLE_SD = getDrawableIdentifier("revanced_video_quality_dialog_button_sd"); private static final int DRAWABLE_HD = getDrawableIdentifier("revanced_video_quality_dialog_button_hd"); @@ -54,11 +57,8 @@ public class VideoQualityDialogButton { private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k"); private static final int DRAWABLE_UNKNOWN = getDrawableIdentifier("revanced_video_quality_dialog_button_unknown"); - private static int getDrawableIdentifier(String resourceName) { - final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); - if (resourceId == 0) Logger.printException(() -> "Could not find resource: " + resourceName); - return resourceId; - } + private static final Animation ANIMATION_SHIMMER = AnimationUtils.loadAnimation(Utils.getContext(), + Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); @Nullable private static PlayerControlButton instance; @@ -68,10 +68,20 @@ private static int getDrawableIdentifier(String resourceName) { */ private static int currentIconResource; + @Nullable + private static Runnable updateIconRunnable; + private static final Handler handler = new Handler(Looper.getMainLooper()); + + private static int getDrawableIdentifier(String resourceName) { + final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); + if (resourceId == 0) Logger.printException(() -> "Could not find resource: " + resourceName); + return resourceId; + } + /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon(@Nullable VideoQuality quality) { + public static void updateButtonIcon(@Nullable VideoQuality quality, boolean updateImmediately) { try { if (instance == null) return; @@ -93,16 +103,25 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { }; if (iconResource != currentIconResource) { - currentIconResource = iconResource; - if (iconResource == DRAWABLE_UNKNOWN) { - instance.setIcon(iconResource); - // Start shimmer animation for unknown state. - Animation shimmer = AnimationUtils.loadAnimation(instance.getView().getContext(), - Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); - instance.startAnimation(shimmer); + cleanup(); + + Runnable update = () -> { + currentIconResource = iconResource; + if (iconResource == DRAWABLE_UNKNOWN) { + instance.setIcon(iconResource); + // Start shimmer animation for unknown state. + instance.startAnimation(ANIMATION_SHIMMER); + } else { + instance.clearAnimation(); // Clear animation for known states. + instance.setIcon(iconResource); + } + }; + + if (updateImmediately) { + update.run(); } else { - instance.clearAnimation(); // Clear animation for known states. - instance.setIcon(iconResource); + updateIconRunnable = update; + handler.postDelayed(update, 300); } } } catch (Exception ex) { @@ -110,6 +129,13 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { } } + public static void cleanup() { + if (updateIconRunnable != null) { + handler.removeCallbacks(updateIconRunnable); + updateIconRunnable = null; + } + } + /** * Injection point. */ @@ -142,7 +168,7 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality); + updateButtonIcon(quality, true); menu.patch_setQuality(quality); return true; } @@ -160,7 +186,7 @@ public static void initializeButton(View controlsView) { ); // Set initial icon. - updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality()); + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -315,12 +341,12 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality); + updateButtonIcon(selectedQuality, true); // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. // Method also handles saving default quality if needed. RememberVideoQualityPatch.userChangedQuality(originalIndex); menu.patch_setQuality(selectedQuality); - + dialog.dismiss(); } catch (Exception ex) { Logger.printException(() -> "Video quality selection failure", ex); From 10bb6ec308fd66d86aa5f20c471b8df08d46a19b Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 08:50:18 -0400 Subject: [PATCH 42/57] revert delayed set icon --- .../quality/RememberVideoQualityPatch.java | 6 +-- .../videoplayer/VideoQualityDialogButton.java | 46 +++++-------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 516dd82e2d..d2ba2217ea 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,7 +156,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); } final int preferredQuality = getDefaultQualityResolution(); @@ -169,7 +169,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); rememberDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality, true); + VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } @@ -258,6 +258,6 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentMenuInterface = null; // Hide the quality button until playback starts and the qualities are available. - VideoQualityDialogButton.updateButtonIcon(null, true); + VideoQualityDialogButton.updateButtonIcon(null); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index d7508e4297..dfbd95713f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -11,8 +11,6 @@ import android.content.res.Configuration; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; -import android.os.Handler; -import android.os.Looper; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; @@ -68,10 +66,6 @@ public class VideoQualityDialogButton { */ private static int currentIconResource; - @Nullable - private static Runnable updateIconRunnable; - private static final Handler handler = new Handler(Looper.getMainLooper()); - private static int getDrawableIdentifier(String resourceName) { final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable"); if (resourceId == 0) Logger.printException(() -> "Could not find resource: " + resourceName); @@ -81,7 +75,7 @@ private static int getDrawableIdentifier(String resourceName) { /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon(@Nullable VideoQuality quality, boolean updateImmediately) { + public static void updateButtonIcon(@Nullable VideoQuality quality) { try { if (instance == null) return; @@ -103,25 +97,14 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda }; if (iconResource != currentIconResource) { - cleanup(); - - Runnable update = () -> { - currentIconResource = iconResource; - if (iconResource == DRAWABLE_UNKNOWN) { - instance.setIcon(iconResource); - // Start shimmer animation for unknown state. - instance.startAnimation(ANIMATION_SHIMMER); - } else { - instance.clearAnimation(); // Clear animation for known states. - instance.setIcon(iconResource); - } - }; - - if (updateImmediately) { - update.run(); + currentIconResource = iconResource; + if (iconResource == DRAWABLE_UNKNOWN) { + instance.setIcon(iconResource); + // Start shimmer animation for unknown state. + instance.startAnimation(ANIMATION_SHIMMER); } else { - updateIconRunnable = update; - handler.postDelayed(update, 300); + instance.clearAnimation(); // Clear animation for known states. + instance.setIcon(iconResource); } } } catch (Exception ex) { @@ -129,13 +112,6 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda } } - public static void cleanup() { - if (updateIconRunnable != null) { - handler.removeCallbacks(updateIconRunnable); - updateIconRunnable = null; - } - } - /** * Injection point. */ @@ -168,7 +144,7 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality, true); + updateButtonIcon(quality); menu.patch_setQuality(quality); return true; } @@ -186,7 +162,7 @@ public static void initializeButton(View controlsView) { ); // Set initial icon. - updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality()); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -341,7 +317,7 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality, true); + updateButtonIcon(selectedQuality); // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. // Method also handles saving default quality if needed. RememberVideoQualityPatch.userChangedQuality(originalIndex); From 917eb99ca0a00db2aa8380f6ee079eaecdb10d16 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 09:00:47 -0400 Subject: [PATCH 43/57] Second attempt to use a delay --- .../quality/RememberVideoQualityPatch.java | 6 ++-- .../videoplayer/VideoQualityDialogButton.java | 35 +++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index d2ba2217ea..516dd82e2d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,7 +156,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); } final int preferredQuality = getDefaultQualityResolution(); @@ -169,7 +169,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); rememberDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality); + VideoQualityDialogButton.updateButtonIcon(quality, true); return userSelectedQualityIndex; } @@ -258,6 +258,6 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentMenuInterface = null; // Hide the quality button until playback starts and the qualities are available. - VideoQualityDialogButton.updateButtonIcon(null); + VideoQualityDialogButton.updateButtonIcon(null, true); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index dfbd95713f..131d32d852 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -75,8 +75,9 @@ private static int getDrawableIdentifier(String resourceName) { /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon(@Nullable VideoQuality quality) { + public static void updateButtonIcon(@Nullable VideoQuality quality, boolean updateImmediately) { try { + Utils.verifyOnMainThread(); if (instance == null) return; final int resolution = quality == null @@ -98,13 +99,27 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { if (iconResource != currentIconResource) { currentIconResource = iconResource; - if (iconResource == DRAWABLE_UNKNOWN) { - instance.setIcon(iconResource); - // Start shimmer animation for unknown state. - instance.startAnimation(ANIMATION_SHIMMER); + + Runnable update = () -> { + if (currentIconResource != iconResource) { + Logger.printDebug(() -> "Ignoring stale update of button icon"); + return; + } + + if (iconResource == DRAWABLE_UNKNOWN) { + instance.setIcon(iconResource); + // Start shimmer animation for unknown state. + instance.startAnimation(ANIMATION_SHIMMER); + } else { + instance.clearAnimation(); // Clear animation for known states. + instance.setIcon(iconResource); + } + }; + + if (updateImmediately) { + update.run(); } else { - instance.clearAnimation(); // Clear animation for known states. - instance.setIcon(iconResource); + Utils.runOnMainThreadDelayed(update, 300); } } } catch (Exception ex) { @@ -144,7 +159,7 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality); + updateButtonIcon(quality, true); menu.patch_setQuality(quality); return true; } @@ -162,7 +177,7 @@ public static void initializeButton(View controlsView) { ); // Set initial icon. - updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality()); + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -317,7 +332,7 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality); + updateButtonIcon(selectedQuality, true); // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. // Method also handles saving default quality if needed. RememberVideoQualityPatch.userChangedQuality(originalIndex); From 921671c8c1864196bdb11e004d3779e7761b8389 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 09:04:48 -0400 Subject: [PATCH 44/57] Adjust delay --- .../extension/youtube/videoplayer/VideoQualityDialogButton.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 131d32d852..a1137f3674 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -119,7 +119,7 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda if (updateImmediately) { update.run(); } else { - Utils.runOnMainThreadDelayed(update, 300); + Utils.runOnMainThreadDelayed(update, 500); } } } catch (Exception ex) { From ed407110f63af25384e6123af8036812712bae04 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 09:18:18 -0400 Subject: [PATCH 45/57] Remove shimmer animation --- .../videoplayer/VideoQualityDialogButton.java | 12 +----------- .../quality/button/VideoQualityDialogButtonPatch.kt | 4 ---- .../revanced_video_quality_dialog_button_shimmer.xml | 10 ---------- 3 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index a1137f3674..2350629daf 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -55,9 +55,6 @@ public class VideoQualityDialogButton { private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k"); private static final int DRAWABLE_UNKNOWN = getDrawableIdentifier("revanced_video_quality_dialog_button_unknown"); - private static final Animation ANIMATION_SHIMMER = AnimationUtils.loadAnimation(Utils.getContext(), - Utils.getResourceIdentifier("revanced_video_quality_dialog_button_shimmer", "anim")); - @Nullable private static PlayerControlButton instance; @@ -106,14 +103,7 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda return; } - if (iconResource == DRAWABLE_UNKNOWN) { - instance.setIcon(iconResource); - // Start shimmer animation for unknown state. - instance.startAnimation(ANIMATION_SHIMMER); - } else { - instance.clearAnimation(); // Clear animation for known states. - instance.setIcon(iconResource); - } + instance.setIcon(iconResource); }; if (updateImmediately) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt index 18a7f258c2..aaa42c8271 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatch.kt @@ -19,10 +19,6 @@ private val videoQualityButtonResourcePatch = resourcePatch { execute { copyResources( "qualitybutton", - ResourceGroup( - "anim", - "revanced_video_quality_dialog_button_shimmer.xml", - ), ResourceGroup( "drawable", "revanced_video_quality_dialog_button_ld.xml", diff --git a/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml b/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml deleted file mode 100644 index e51e54ee3d..0000000000 --- a/patches/src/main/resources/qualitybutton/anim/revanced_video_quality_dialog_button_shimmer.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - From b9e3cf1378410f30013ec6f5e071521c0fae2c1a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 09:20:24 -0400 Subject: [PATCH 46/57] revert second attempt to use a delay (icons can still be incorrect) --- .../quality/RememberVideoQualityPatch.java | 6 ++--- .../videoplayer/VideoQualityDialogButton.java | 26 ++++--------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 516dd82e2d..d2ba2217ea 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,7 +156,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality, false); + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); } final int preferredQuality = getDefaultQualityResolution(); @@ -169,7 +169,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); rememberDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality, true); + VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } @@ -258,6 +258,6 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl currentMenuInterface = null; // Hide the quality button until playback starts and the qualities are available. - VideoQualityDialogButton.updateButtonIcon(null, true); + VideoQualityDialogButton.updateButtonIcon(null); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 2350629daf..29a7e94492 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -22,7 +22,6 @@ import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; -import android.view.animation.AnimationUtils; import android.view.animation.TranslateAnimation; import android.widget.ArrayAdapter; import android.widget.ImageView; @@ -72,7 +71,7 @@ private static int getDrawableIdentifier(String resourceName) { /** * Updates the button icon based on the current video quality. */ - public static void updateButtonIcon(@Nullable VideoQuality quality, boolean updateImmediately) { + public static void updateButtonIcon(@Nullable VideoQuality quality) { try { Utils.verifyOnMainThread(); if (instance == null) return; @@ -81,7 +80,6 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda ? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading. : quality.patch_getResolution(); - // Map quality to appropriate icon. final int iconResource = switch (resolution) { case 144, 240, 360 -> DRAWABLE_LD; case 480 -> DRAWABLE_SD; @@ -96,21 +94,7 @@ public static void updateButtonIcon(@Nullable VideoQuality quality, boolean upda if (iconResource != currentIconResource) { currentIconResource = iconResource; - - Runnable update = () -> { - if (currentIconResource != iconResource) { - Logger.printDebug(() -> "Ignoring stale update of button icon"); - return; - } - - instance.setIcon(iconResource); - }; - - if (updateImmediately) { - update.run(); - } else { - Utils.runOnMainThreadDelayed(update, 500); - } + instance.setIcon(iconResource); } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); @@ -149,7 +133,7 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality, true); + updateButtonIcon(quality); menu.patch_setQuality(quality); return true; } @@ -167,7 +151,7 @@ public static void initializeButton(View controlsView) { ); // Set initial icon. - updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality(), true); + updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality()); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -322,7 +306,7 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality, true); + updateButtonIcon(selectedQuality); // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. // Method also handles saving default quality if needed. RememberVideoQualityPatch.userChangedQuality(originalIndex); From ef582bc97cb0922e9d57177e3da897734b78137d Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 10:58:39 -0400 Subject: [PATCH 47/57] fix flickering? --- .../quality/RememberVideoQualityPatch.java | 8 ++++---- .../videoplayer/VideoQualityDialogButton.java | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index d2ba2217ea..560f35dd70 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -89,7 +89,7 @@ public static VideoQualityMenuInterface getCurrentMenuInterface() { return currentMenuInterface; } - private static boolean shouldRememberVideoQuality() { + public static boolean shouldRememberVideoQuality() { BooleanSetting preference = ShortsPlayerState.isOpen() ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; @@ -104,7 +104,7 @@ public static int getDefaultQualityResolution() { return preference.get(); } - private static void rememberDefaultQuality(int qualityResolution) { + public static void saveDefaultQuality(int qualityResolution) { final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; IntegerSetting qualitySetting; @@ -168,7 +168,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte userChangedDefaultQuality = false; VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); - rememberDefaultQuality(quality.patch_getResolution()); + saveDefaultQuality(quality.patch_getResolution()); VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } @@ -241,7 +241,7 @@ public static void userChangedQuality(int qualityIndex) { public static void userChangedQualityInFlyout(int videoResolution) { Utils.verifyOnMainThread(); if (shouldRememberVideoQuality()) { - rememberDefaultQuality(videoResolution); + saveDefaultQuality(videoResolution); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 29a7e94492..2454a5f848 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -94,7 +94,14 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { if (iconResource != currentIconResource) { currentIconResource = iconResource; - instance.setIcon(iconResource); + + Utils.runOnMainThreadDelayed(() -> { + if (iconResource != currentIconResource) { + Logger.printDebug(() -> "Ignoring stale button update to: " + quality); + return; + } + instance.setIcon(iconResource); + }, 100); } } catch (Exception ex) { Logger.printException(() -> "updateButtonIcon failure", ex); @@ -307,11 +314,12 @@ private static void showVideoQualityDialog(Context context) { VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); updateButtonIcon(selectedQuality); - // Must override index, otherwise picking 1080p will always use 1080p Enhanced if available. - // Method also handles saving default quality if needed. - RememberVideoQualityPatch.userChangedQuality(originalIndex); menu.patch_setQuality(selectedQuality); + if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { + RememberVideoQualityPatch.saveDefaultQuality(selectedQuality.patch_getResolution()); + } + dialog.dismiss(); } catch (Exception ex) { Logger.printException(() -> "Video quality selection failure", ex); From 496a46b40915b05b4c3f6fc70dee678884ac7a43 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:28:45 -0400 Subject: [PATCH 48/57] fix 1080p uses 1080p Premium? --- .../youtube/videoplayer/VideoQualityDialogButton.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 2454a5f848..3ab1bfb9f0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -315,10 +315,7 @@ private static void showVideoQualityDialog(Context context) { Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); updateButtonIcon(selectedQuality); menu.patch_setQuality(selectedQuality); - - if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { - RememberVideoQualityPatch.saveDefaultQuality(selectedQuality.patch_getResolution()); - } + RememberVideoQualityPatch.userChangedQuality(originalIndex); dialog.dismiss(); } catch (Exception ex) { From 902c088b28dea3aeccdf0d8990ccb55aadc22eef Mon Sep 17 00:00:00 2001 From: MarcaDian <152095496+MarcaDian@users.noreply.github.com> Date: Sat, 2 Aug 2025 19:39:26 +0300 Subject: [PATCH 49/57] change 'FHD_PLUS' icon --- .../drawable/revanced_video_quality_dialog_button_fhd_plus.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml index 968f40119c..18e163ca2b 100644 --- a/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml +++ b/patches/src/main/resources/qualitybutton/drawable/revanced_video_quality_dialog_button_fhd_plus.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:pathData="M3.61719,5 C3.15625,5,2.76953,5.15234,2.46094,5.46094 C2.15234,5.76953,2,6.15625,2,6.61719 L2,17.3828 C2,17.8438,2.15234,18.2305,2.46094,18.5391 C2.76953,18.8477,3.15625,19,3.61719,19 L20.3828,19 C20.8438,19,21.2305,18.8477,21.5391,18.5391 C21.8477,18.2305,22,17.8438,22,17.3828 L22,6.61719 C22,6.15625,21.8477,5.76953,21.5391,5.46094 C21.2305,5.15234,20.8438,5,20.3828,5 L3.61719,5 Z M3.61719,6 L20.3828,6 C20.5625,6,20.7109,6.05859,20.8281,6.17188 C20.9414,6.28906,21,6.4375,21,6.61719 L21,17.3828 C21,17.5625,20.9414,17.7109,20.8281,17.8281 C20.7109,17.9414,20.5625,18,20.3828,18 L3.61719,18 C3.4375,18,3.28906,17.9414,3.17188,17.8281 C3.05859,17.7109,3,17.5625,3,17.3828 L3,6.61719 C3,6.4375,3.05859,6.28906,3.17188,6.17188 C3.28906,6.05859,3.4375,6,3.61719,6 Z M5.03906,9.30859 L5.03906,14.6914 L5.92188,14.6914 L5.92188,12.6172 L7.92188,12.6172 L7.92188,11.7305 L5.92188,11.7305 L5.92188,10.1914 L8.42188,10.1914 L8.42188,9.30859 L5.03906,9.30859 Z M9.80859,9.30859 L9.80859,14.6914 L10.6914,14.6914 L10.6914,12.6914 L12.8086,12.6914 L12.8086,14.6914 L13.6914,14.6914 L13.6914,9.30859 L12.8086,9.30859 L12.8086,11.8086 L10.6914,11.8086 L10.6914,9.30859 L9.80859,9.30859 Z M15.0781,9.30859 L15.0781,14.6914 L17.8828,14.6914 C18.1797,14.6914,18.4336,14.5859,18.6445,14.375 C18.8555,14.1641,18.9609,13.9102,18.9609,13.6172 L18.9609,10.3828 C18.9609,10.0898,18.8555,9.83594,18.6445,9.625 C18.4336,9.41406,18.1797,9.30859,17.8828,9.30859 L15.0781,9.30859 Z M15.9609,10.1914 L17.7695,10.1914 C17.8477,10.1914,17.918,10.2227,17.9805,10.2891 C18.043,10.3516,18.0781,10.4219,18.0781,10.5 L18.0781,13.5 C18.0781,13.5781,18.043,13.6484,17.9805,13.7109 C17.918,13.7773,17.8477,13.8086,17.7695,13.8086 L15.9609,13.8086 L15.9609,10.1914 Z M5.03906,15.5098 L5.03906,16.3926 L18.9609,16.3926 L18.9609,15.5098 L5.03906,15.5098 Z" /> From d1708e07999ddd9a20f6bc744ecda1349b221f94 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:52:56 -0400 Subject: [PATCH 50/57] fix button showing the wrong icon if YT doesn't want to change from 1080p Premium to regular 1080p --- .../youtube/videoplayer/VideoQualityDialogButton.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 3ab1bfb9f0..201c01ff1e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -313,7 +313,9 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - updateButtonIcon(selectedQuality); + // Don't update button icon now. Icon will be updated when the actual + // quality is changed by YT. This is needed to ensure the icon is correct + // if YT ignores changing from 1080p Premium to regular 1080p. menu.patch_setQuality(selectedQuality); RememberVideoQualityPatch.userChangedQuality(originalIndex); From 3f122d038fe1cc560cd89166fac088a92929022f Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:58:51 -0400 Subject: [PATCH 51/57] cleanup --- .../patches/playback/quality/RememberVideoQualityPatch.java | 4 +++- .../youtube/videoplayer/VideoQualityDialogButton.java | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 560f35dd70..1444b3ace3 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,7 +156,9 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); + if (!userChangedDefaultQuality) { + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); + } } final int preferredQuality = getDefaultQualityResolution(); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 201c01ff1e..8ea6d989c5 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -140,7 +140,6 @@ public static void initializeButton(View controlsView) { final int resolution = quality.patch_getResolution(); if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) { Logger.printDebug(() -> "Resetting quality to: " + quality); - updateButtonIcon(quality); menu.patch_setQuality(quality); return true; } From 53fcdb69778527bb4b1e2c8a93f0e5e54bfef548 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:05:42 -0400 Subject: [PATCH 52/57] Fix wrong 1080p Premium icon if remember quality is enabled --- .../patches/playback/quality/RememberVideoQualityPatch.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 1444b3ace3..a6dd48fe4a 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -156,9 +156,7 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte currentQuality = updatedCurrentQuality; Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality); - if (!userChangedDefaultQuality) { - VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); - } + VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality); } final int preferredQuality = getDefaultQualityResolution(); @@ -171,7 +169,6 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte VideoQuality quality = qualities[userSelectedQualityIndex]; Logger.printDebug(() -> "User changed default quality to: " + quality); saveDefaultQuality(quality.patch_getResolution()); - VideoQualityDialogButton.updateButtonIcon(quality); return userSelectedQualityIndex; } From f22f18e57500ef70e43edd328272e98cec9bdc31 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:07:52 -0400 Subject: [PATCH 53/57] don't show quality toast if saved quality did not change --- .../patches/playback/quality/RememberVideoQualityPatch.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index a6dd48fe4a..9c33e15862 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -115,6 +115,12 @@ public static void saveDefaultQuality(int qualityResolution) { networkTypeMessage = str("revanced_remember_video_quality_wifi"); qualitySetting = shortPlayerOpen ? shortsQualityWifi : videoQualityWifi; } + + if (qualitySetting.get() == qualityResolution) { + // User clicked the same video quality as the current video, + // or changed between 1080p Premium and non-Premium. + return; + } qualitySetting.save(qualityResolution); if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { From d15427d1456084d08207d53129c9bf104f313adc Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 19:13:52 -0400 Subject: [PATCH 54/57] Cleanup, comments --- .../AdvancedVideoQualityMenuPatch.java | 24 +++---- .../quality/RememberVideoQualityPatch.java | 69 ++++++++----------- .../videoplayer/VideoQualityDialogButton.java | 7 +- .../hook/RecyclerViewTreeHookPatch.kt | 1 - .../quality/AdvancedVideoQualityMenuPatch.kt | 3 +- .../youtube/video/quality/Fingerprints.kt | 2 +- .../quality/RememberVideoQualityPatch.kt | 21 ++---- 7 files changed, 54 insertions(+), 73 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/AdvancedVideoQualityMenuPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/AdvancedVideoQualityMenuPatch.java index 6722c4cb7c..89f221b3be 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/AdvancedVideoQualityMenuPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/AdvancedVideoQualityMenuPatch.java @@ -18,7 +18,7 @@ public final class AdvancedVideoQualityMenuPatch { /** - * Injection point. + * Injection point. Regular videos. */ public static void onFlyoutMenuCreate(RecyclerView recyclerView) { if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return; @@ -61,22 +61,12 @@ public static void onFlyoutMenuCreate(RecyclerView recyclerView) { }); } - - /** - * Injection point. - * - * Used to force the creation of the advanced menu item for the Shorts quality flyout. - */ - public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) { - return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original; - } - /** * Injection point. * * Shorts video quality flyout. */ - public static void showAdvancedVideoQualityMenu(ListView listView) { + public static void addVideoQualityListMenuListener(ListView listView) { if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return; listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { @@ -91,7 +81,6 @@ public void onChildViewAdded(View parent, View child) { listView.setSoundEffectsEnabled(false); final var qualityItemMenuPosition = 4; listView.performItemClick(null, qualityItemMenuPosition, 0); - } catch (Exception ex) { Logger.printException(() -> "showAdvancedVideoQualityMenu failure", ex); } @@ -102,4 +91,13 @@ public void onChildViewRemoved(View parent, View child) { } }); } + + /** + * Injection point. + * + * Used to force the creation of the advanced menu item for the Shorts quality flyout. + */ + public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) { + return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original; + } } \ No newline at end of file diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 9c33e15862..703a3c6d44 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -44,18 +44,6 @@ public interface VideoQualityMenuInterface { private static boolean qualityNeedsUpdating; - /** - * If the user selected a new quality from the flyout menu, - * and {@link Settings#REMEMBER_VIDEO_QUALITY_LAST_SELECTED} - * or {@link Settings#REMEMBER_SHORTS_QUALITY_LAST_SELECTED} is enabled. - */ - private static boolean userChangedDefaultQuality; - - /** - * Index of the video quality chosen by the user from the flyout menu. - */ - private static int userSelectedQualityIndex; - /** * The available qualities of the current video. */ @@ -166,18 +154,10 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte } final int preferredQuality = getDefaultQualityResolution(); - if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { + if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { return originalQualityIndex; // Nothing to do. } - if (userChangedDefaultQuality) { - userChangedDefaultQuality = false; - VideoQuality quality = qualities[userSelectedQualityIndex]; - Logger.printDebug(() -> "User changed default quality to: " + quality); - saveDefaultQuality(quality.patch_getResolution()); - return userSelectedQualityIndex; - } - // After changing videos the qualities can initially be for the prior video. // If the qualities have changed and the default is not auto then an update is needed. if (!qualityNeedsUpdating && !availableQualitiesChanged) { @@ -216,35 +196,32 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte return originalQualityIndex; } - /** - * Injection point. Fixes bad data used by YouTube. - */ - public static int fixVideoQualityResolution(String name, int quality) { - final int correctQuality = 480; - if (name.equals("480p") && quality != correctQuality) { - return correctQuality; - } - - return quality; - } - /** * Injection point. - * @param qualityIndex Element index of {@link #currentQualities}. + * @param userSelectedQualityIndex Element index of {@link #currentQualities}. */ - public static void userChangedQuality(int qualityIndex) { - if (shouldRememberVideoQuality()) { - userSelectedQualityIndex = qualityIndex; - userChangedDefaultQuality = true; + public static void userChangedShortsQuality(int userSelectedQualityIndex) { + try { + if (shouldRememberVideoQuality()) { + if (currentQualities == null) { + Logger.printDebug(() -> "Cannot save default quality, qualities is null"); + return; + } + VideoQuality quality = currentQualities.get(userSelectedQualityIndex); + saveDefaultQuality(quality.patch_getResolution()); + } + } catch (Exception ex) { + Logger.printException(() -> "userChangedShortsQuality failure", ex); } } /** - * Injection point. + * Injection point. Regular videos. * @param videoResolution Human readable resolution: 480, 720, 1080. */ - public static void userChangedQualityInFlyout(int videoResolution) { + public static void userChangedQuality(int videoResolution) { Utils.verifyOnMainThread(); + if (shouldRememberVideoQuality()) { saveDefaultQuality(videoResolution); } @@ -265,4 +242,16 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl // Hide the quality button until playback starts and the qualities are available. VideoQualityDialogButton.updateButtonIcon(null); } + + /** + * Injection point. Fixes bad data used by YouTube. + */ + public static int fixVideoQualityResolution(String name, int quality) { + final int correctQuality = 480; + if (name.equals("480p") && quality != correctQuality) { + return correctQuality; + } + + return quality; + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index 8ea6d989c5..fa898b35d3 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -312,11 +312,14 @@ private static void showVideoQualityDialog(Context context) { final int originalIndex = which + 1; // Adjust for automatic. VideoQuality selectedQuality = currentQualities.get(originalIndex); Logger.printDebug(() -> "User clicked on quality: " + selectedQuality); - // Don't update button icon now. Icon will be updated when the actual + + if (RememberVideoQualityPatch.shouldRememberVideoQuality()) { + RememberVideoQualityPatch.saveDefaultQuality(selectedQuality.patch_getResolution()); + } + // Don't update button icon now. Icon will update when the actual // quality is changed by YT. This is needed to ensure the icon is correct // if YT ignores changing from 1080p Premium to regular 1080p. menu.patch_setQuality(selectedQuality); - RememberVideoQualityPatch.userChangedQuality(originalIndex); dialog.dismiss(); } catch (Exception ex) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt index 86588df099..58033a223e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/recyclerviewtree/hook/RecyclerViewTreeHookPatch.kt @@ -11,7 +11,6 @@ val recyclerViewTreeHookPatch = bytecodePatch { dependsOn(sharedExtensionPatch) execute { - recyclerViewTreeObserverFingerprint.method.apply { val insertIndex = recyclerViewTreeObserverFingerprint.patternMatch!!.startIndex + 1 val recyclerViewParameter = 2 diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt index 55285a56e3..3e8aa23a17 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt @@ -68,7 +68,6 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch { // region Patch for the old type of the video quality menu. // Used for regular videos when spoofing to old app version, // and for the Shorts quality flyout on newer app versions. - videoQualityMenuViewInflateFingerprint.let { it.method.apply { val checkCastIndex = it.patternMatch!!.endIndex @@ -77,7 +76,7 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch { addInstruction( checkCastIndex + 1, "invoke-static { v$listViewRegister }, $EXTENSION_CLASS_DESCRIPTOR->" + - "showAdvancedVideoQualityMenu(Landroid/widget/ListView;)V", + "addVideoQualityListMenuListener(Landroid/widget/ListView;)V", ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt index c98bdac633..f040796325 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt @@ -23,7 +23,7 @@ internal val videoQualityFingerprint = fingerprint { /** * Matches with the class found in [videoQualitySetterFingerprint]. */ -internal val setQualityByIndexMethodClassFieldReferenceFingerprint = fingerprint { +internal val setVideoQualityFingerprint = fingerprint { returns("V") parameters("L") opcodes( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index b5ecce54ba..7a31a6b74c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -67,13 +67,6 @@ val rememberVideoQualityPatch = bytecodePatch { SwitchPreference("revanced_remember_video_quality_last_selected_toast") )) - /* - * The following code works by hooking the method which is called when the user selects a video quality - * to remember the last selected video quality. - * - * It also hooks the method which is called when the video quality to set is determined. - * Conveniently, at this point the video quality is overridden to the remembered playback speed. - */ onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted") videoQualityFingerprint.let { @@ -142,10 +135,10 @@ val rememberVideoQualityPatch = bytecodePatch { } // Inject a call to set the remembered quality once a video loads. - setQualityByIndexMethodClassFieldReferenceFingerprint.match( + setVideoQualityFingerprint.match( videoQualitySetterFingerprint.originalClassDef ).let { match -> - // This instruction refers to the field with the type that contains the setQualityByIndex method. + // This instruction refers to the field with the type that contains the setQuality method. val instructions = match.method.implementation!!.instructions val onItemClickListenerClassReference = (instructions.elementAt(0) as ReferenceInstruction).reference @@ -191,7 +184,7 @@ val rememberVideoQualityPatch = bytecodePatch { videoQualitySetterFingerprint.method.addInstructions( 0, """ - # Get the object instance to invoke the setQualityByIndex method on. + # Get object instance to invoke setQuality method. iget-object v0, p0, $onItemClickListenerClassReference iget-object v0, v0, $setQualityFieldReference @@ -201,15 +194,15 @@ val rememberVideoQualityPatch = bytecodePatch { ) } - // Inject a call to remember the selected quality. + // Inject a call to remember the selected quality for Shorts. videoQualityItemOnClickFingerprint.match( videoQualityItemOnClickParentFingerprint.classDef ).method.addInstruction( 0, - "invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V" + "invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V" ) - // Inject a call to remember the user selected quality. + // Inject a call to remember the user selected quality for regular videos. videoQualityChangedFingerprint.let { it.method.apply { val index = it.patternMatch!!.startIndex @@ -217,7 +210,7 @@ val rememberVideoQualityPatch = bytecodePatch { addInstruction( index + 1, - "invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQualityInFlyout(I)V", + "invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V", ) } } From 1d49adefc157212ccbe83f1213125043561daaf0 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 19:28:04 -0400 Subject: [PATCH 55/57] Cleanup --- .../quality/RememberVideoQualityPatch.java | 28 ++++++++++--------- .../videoplayer/VideoQualityDialogButton.java | 4 +-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 703a3c6d44..f7d5043bef 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -29,13 +29,16 @@ public interface VideoQualityMenuInterface { void patch_setQuality(VideoQuality quality); } + /** + * Video resolution of the automatic quality option.. + */ public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; /** - * All video names are the same for all languages, including enhanced bitrate. + * All quality names are the same for all languages. * VideoQuality also has a resolution enum that can be used if needed. */ - public static final String VIDEO_QUALITY_1080P_ENHANCED = "1080p Premium"; + public static final String VIDEO_QUALITY_1080P_PREMIUM_NAME = "1080p Premium"; private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; @@ -51,7 +54,8 @@ public interface VideoQualityMenuInterface { private static List currentQualities; /** - * The current quality of the video playing. This can never be the automatic value. + * The current quality of the video playing. + * This is always the actual quality even if Automatic quality is active. */ @Nullable private static VideoQuality currentQuality; @@ -78,8 +82,8 @@ public static VideoQualityMenuInterface getCurrentMenuInterface() { } public static boolean shouldRememberVideoQuality() { - BooleanSetting preference = ShortsPlayerState.isOpen() ? - Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED + BooleanSetting preference = ShortsPlayerState.isOpen() + ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; return preference.get(); } @@ -172,19 +176,17 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte if (qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality) { // If the desired quality index is equal to the original index, // then the video is already set to the desired default quality. - if (i == originalQualityIndex) { - Logger.printDebug(() -> "Video is already preferred quality: " + quality); - } else { - Logger.printDebug(() -> "Changing video quality from: " - + qualities[originalQualityIndex] + " to: " + quality); - } + final int index = i; + Logger.printDebug(() -> index == originalQualityIndex + ? "Video is already the preferred quality: " + quality + : "Changing video quality from: " + qualities[originalQualityIndex] + " to: " + quality + ); // On first load of a new video, if the video is already the desired quality // then the quality flyout will show 'Auto' (ie: Auto (720p)). // // To prevent user confusion, set the video index even if the // quality is already correct so the UI picker will not display "Auto". - Logger.printDebug(() -> "Changing quality to default: " + quality); menu.patch_setQuality(qualities[i]); return i; } @@ -234,10 +236,10 @@ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPl Utils.verifyOnMainThread(); Logger.printDebug(() -> "newVideoStarted"); - qualityNeedsUpdating = true; currentQualities = null; currentQuality = null; currentMenuInterface = null; + qualityNeedsUpdating = true; // Hide the quality button until playback starts and the qualities are available. VideoQualityDialogButton.updateButtonIcon(null); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java index fa898b35d3..924f1ce7b1 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java @@ -3,7 +3,7 @@ import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.dipToPixels; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE; -import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VIDEO_QUALITY_1080P_ENHANCED; +import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VIDEO_QUALITY_1080P_PREMIUM_NAME; import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VideoQualityMenuInterface; import android.app.Dialog; @@ -84,7 +84,7 @@ public static void updateButtonIcon(@Nullable VideoQuality quality) { case 144, 240, 360 -> DRAWABLE_LD; case 480 -> DRAWABLE_SD; case 720 -> DRAWABLE_HD; - case 1080 -> VIDEO_QUALITY_1080P_ENHANCED.equals(quality.patch_getQualityName()) + case 1080 -> VIDEO_QUALITY_1080P_PREMIUM_NAME.equals(quality.patch_getQualityName()) ? DRAWABLE_FHD_PLUS : DRAWABLE_FHD; case 1440 -> DRAWABLE_QHD; From 7848fd31dd7b503ee63783f33c96e70acb02aa77 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 19:32:50 -0400 Subject: [PATCH 56/57] Remove unused code --- .../patches/playback/quality/RememberVideoQualityPatch.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index f7d5043bef..40c6e1b514 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -116,9 +116,7 @@ public static void saveDefaultQuality(int qualityResolution) { qualitySetting.save(qualityResolution); if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { - String qualityLabel = qualityResolution == AUTOMATIC_VIDEO_QUALITY_VALUE - ? str("video_quality_quick_menu_auto_toast") - : qualityResolution + "p"; + String qualityLabel = qualityResolution + "p"; Utils.showToastShort(str( shortPlayerOpen ? "revanced_remember_video_quality_toast_shorts" From ebaa1953c6b40dfe5fd3692a37c7c971daa88e91 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 2 Aug 2025 20:24:30 -0400 Subject: [PATCH 57/57] Fix Shorts stuttering/restarting on first load if a default quality is used --- .../quality/RememberVideoQualityPatch.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 40c6e1b514..dcbad858bc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -172,21 +172,27 @@ public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInte for (VideoQuality quality : qualities) { final int qualityResolution = quality.patch_getResolution(); if (qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality) { - // If the desired quality index is equal to the original index, - // then the video is already set to the desired default quality. - final int index = i; - Logger.printDebug(() -> index == originalQualityIndex - ? "Video is already the preferred quality: " + quality - : "Changing video quality from: " + qualities[originalQualityIndex] + " to: " + quality + final boolean qualityNeedsChange = (i != originalQualityIndex); + Logger.printDebug(() -> qualityNeedsChange + ? "Changing video quality from: " + updatedCurrentQuality + " to: " + quality + : "Video is already the preferred quality: " + quality ); - // On first load of a new video, if the video is already the desired quality - // then the quality flyout will show 'Auto' (ie: Auto (720p)). + // On first load of a new regular video, if the video is already the + // desired quality then the quality flyout will show 'Auto' (ie: Auto (720p)). // // To prevent user confusion, set the video index even if the // quality is already correct so the UI picker will not display "Auto". - menu.patch_setQuality(qualities[i]); - return i; + // + // Only change Shorts quality if the quality actually needs to change, + // because the "auto" option is not shown in the flyout + // and setting the same quality again can cause the Short to restart. + if (qualityNeedsChange || !ShortsPlayerState.isOpen()) { + menu.patch_setQuality(qualities[i]); + return i; + } + + return originalQualityIndex; } i++; }