mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-15 21:52:27 +01:00
Compare commits
51 Commits
v5.31.3-de
...
v5.33.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cfc31c8f7 | ||
|
|
a28891e5f3 | ||
|
|
36036b082d | ||
|
|
1bc63e50a7 | ||
|
|
4b2b5e3029 | ||
|
|
9afa7d2ac6 | ||
|
|
1a8146dbc8 | ||
|
|
178eed7fcd | ||
|
|
621292644c | ||
|
|
1dd01cf54a | ||
|
|
8c31374c53 | ||
|
|
2e177a8839 | ||
|
|
cfffd422f8 | ||
|
|
37aab8382e | ||
|
|
f4950ec2ea | ||
|
|
7bdc32867a | ||
|
|
6e60ac6963 | ||
|
|
1adbd563b2 | ||
|
|
9ccf13b680 | ||
|
|
7b8ca9c018 | ||
|
|
ae6dd23d08 | ||
|
|
b1d164b446 | ||
|
|
87c39dd485 | ||
|
|
1549ac12aa | ||
|
|
5d08fdddb8 | ||
|
|
98114e5bde | ||
|
|
a4817dfdd0 | ||
|
|
d4f05351e1 | ||
|
|
d92362b0d9 | ||
|
|
afc7c75df1 | ||
|
|
f0d4e9bfb4 | ||
|
|
e9e4cf39b6 | ||
|
|
0579a9f760 | ||
|
|
1c0acef3f3 | ||
|
|
2419adb77b | ||
|
|
9e4113555b | ||
|
|
125855540b | ||
|
|
a8eee825e6 | ||
|
|
63859f0ef9 | ||
|
|
1c9000dbda | ||
|
|
8ec857a175 | ||
|
|
f56c7868f5 | ||
|
|
cfd77800d6 | ||
|
|
707deaef0b | ||
|
|
9ddb3ac39d | ||
|
|
a7d3b7c287 | ||
|
|
30bac0397e | ||
|
|
c5fc187a35 | ||
|
|
f46dbcd084 | ||
|
|
2136573cb6 | ||
|
|
86ec08993c |
142
CHANGELOG.md
142
CHANGELOG.md
@@ -1,3 +1,145 @@
|
||||
# [5.33.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.12...v5.33.0-dev.13) (2025-08-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Messenger - Hide Facebook button:** Support the latest app version ([#5590](https://github.com/ReVanced/revanced-patches/issues/5590)) ([0cab98d](https://github.com/ReVanced/revanced-patches/commit/0cab98df1689dbf7a042f18f4a961d47da1430ad))
|
||||
|
||||
# [5.33.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.11...v5.33.0-dev.12) (2025-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([295f0f2](https://github.com/ReVanced/revanced-patches/commit/295f0f216b5e8aa9d68457862e73e312b7342703))
|
||||
|
||||
# [5.33.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.10...v5.33.0-dev.11) (2025-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Video quality:** Fix 144p default not always used ([2f7483a](https://github.com/ReVanced/revanced-patches/commit/2f7483a2d789c28a243b58bb7a252c0d590858ee))
|
||||
|
||||
# [5.33.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.9...v5.33.0-dev.10) (2025-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f))
|
||||
|
||||
# [5.33.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.8...v5.33.0-dev.9) (2025-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([9fe13ee](https://github.com/ReVanced/revanced-patches/commit/9fe13ee1af104c009efd19b826adef375e48e191))
|
||||
|
||||
# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([cd3a6be](https://github.com/ReVanced/revanced-patches/commit/cd3a6be75c6bd3cc33c0b17a044bd6147f27b5ce))
|
||||
|
||||
# [5.33.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.6...v5.33.0-dev.7) (2025-08-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([d5f51bf](https://github.com/ReVanced/revanced-patches/commit/d5f51bf400dd22626ff65d7563b6fde70d53fb25))
|
||||
|
||||
# [5.33.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.5...v5.33.0-dev.6) (2025-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([bd3ace0](https://github.com/ReVanced/revanced-patches/commit/bd3ace0bd04ccd0369adb49d63aa0cf986402346))
|
||||
|
||||
# [5.33.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.4...v5.33.0-dev.5) (2025-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([bf29d69](https://github.com/ReVanced/revanced-patches/commit/bf29d6909e389819bad878ad3b94bbc90d823cc9))
|
||||
|
||||
# [5.33.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.3...v5.33.0-dev.4) (2025-07-30)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([ccac46e](https://github.com/ReVanced/revanced-patches/commit/ccac46eebc2e14b094454e37ef4461d48a62c53f))
|
||||
|
||||
# [5.33.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.2...v5.33.0-dev.3) (2025-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([1e8f436](https://github.com/ReVanced/revanced-patches/commit/1e8f4368e117f4b278c24709231cb32546e46dc0))
|
||||
|
||||
# [5.33.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.1...v5.33.0-dev.2) (2025-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([6c6aa35](https://github.com/ReVanced/revanced-patches/commit/6c6aa35411a139dddc3a15dd757fbeded5d1a0a3))
|
||||
|
||||
# [5.33.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0-dev.1) (2025-07-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([b01f15b](https://github.com/ReVanced/revanced-patches/commit/b01f15b9acb0427aed99b0141ae271831b7936bf))
|
||||
|
||||
# [5.32.0](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.32.0) (2025-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252))
|
||||
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068))
|
||||
* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
|
||||
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a))
|
||||
* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc))
|
||||
|
||||
# [5.32.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.4...v5.32.0-dev.5) (2025-07-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc))
|
||||
|
||||
# [5.32.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.3...v5.32.0-dev.4) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252))
|
||||
|
||||
# [5.32.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.2...v5.32.0-dev.3) (2025-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a))
|
||||
|
||||
# [5.32.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.1...v5.32.0-dev.2) (2025-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068))
|
||||
|
||||
# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
|
||||
|
||||
## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package app.revanced.extension.primevideo.videoplayer;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PixelFormat;
|
||||
import java.util.Arrays;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
import com.amazon.video.sdk.player.Player;
|
||||
|
||||
public class PlaybackSpeedPatch {
|
||||
private static Player player;
|
||||
private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f};
|
||||
private static final String SPEED_BUTTON_TAG = "speed_overlay";
|
||||
|
||||
public static void setPlayer(Player playerInstance) {
|
||||
player = playerInstance;
|
||||
if (player != null) {
|
||||
// Reset playback rate when switching between episodes to ensure correct display.
|
||||
player.setPlaybackRate(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeSpeedOverlay(View userControlsView) {
|
||||
try {
|
||||
LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop");
|
||||
|
||||
// If the speed overlay exists we should return early.
|
||||
if (Utils.getChildView(buttonContainer, false, child ->
|
||||
child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageView speedButton = createSpeedButton(userControlsView.getContext());
|
||||
speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton));
|
||||
buttonContainer.addView(speedButton, 0);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logger.printException(() -> "initializeSpeedOverlay, no button container found", e);
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "initializeSpeedOverlay failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageView createSpeedButton(Context context) {
|
||||
ImageView speedButton = new ImageView(context);
|
||||
speedButton.setContentDescription("Playback Speed");
|
||||
speedButton.setTag(SPEED_BUTTON_TAG);
|
||||
speedButton.setClickable(true);
|
||||
speedButton.setFocusable(true);
|
||||
speedButton.setScaleType(ImageView.ScaleType.CENTER);
|
||||
|
||||
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
|
||||
speedButton.setImageDrawable(speedIcon);
|
||||
|
||||
int buttonSize = Utils.dipToPixels(48);
|
||||
speedButton.setMinimumWidth(buttonSize);
|
||||
speedButton.setMinimumHeight(buttonSize);
|
||||
|
||||
return speedButton;
|
||||
}
|
||||
|
||||
private static String[] getSpeedOptions() {
|
||||
String[] options = new String[SPEED_VALUES.length];
|
||||
for (int i = 0; i < SPEED_VALUES.length; i++) {
|
||||
options[i] = SPEED_VALUES[i] + "x";
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void changePlaybackSpeed(ImageView imageView) {
|
||||
if (player == null) {
|
||||
Logger.printException(() -> "Player not available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
player.pause();
|
||||
AlertDialog dialog = createSpeedPlaybackDialog(imageView);
|
||||
dialog.setOnDismissListener(dialogInterface -> player.play());
|
||||
dialog.show();
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "changePlaybackSpeed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) {
|
||||
Context context = imageView.getContext();
|
||||
int currentSelection = getCurrentSpeedSelection();
|
||||
|
||||
return new AlertDialog.Builder(context)
|
||||
.setTitle("Select Playback Speed")
|
||||
.setSingleChoiceItems(getSpeedOptions(), currentSelection,
|
||||
PlaybackSpeedPatch::handleSpeedSelection)
|
||||
.create();
|
||||
}
|
||||
|
||||
private static int getCurrentSpeedSelection() {
|
||||
try {
|
||||
float currentRate = player.getPlaybackRate();
|
||||
int index = Arrays.binarySearch(SPEED_VALUES, currentRate);
|
||||
return Math.max(index, 0); // Use slowest speed if not found.
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) {
|
||||
try {
|
||||
float selectedSpeed = SPEED_VALUES[selectedIndex];
|
||||
player.setPlaybackRate(selectedSpeed);
|
||||
player.play();
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "handleSpeedSelection error setting playback speed", e);
|
||||
} finally {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedIconDrawable extends Drawable {
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int w = getBounds().width();
|
||||
int h = getBounds().height();
|
||||
float centerX = w / 2f;
|
||||
// Position gauge in lower portion.
|
||||
float centerY = h * 0.7f;
|
||||
float radius = Math.min(w, h) / 2f * 0.8f;
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(radius * 0.1f);
|
||||
|
||||
// Draw semicircle.
|
||||
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
|
||||
canvas.drawArc(oval, 180, 180, false, paint);
|
||||
|
||||
// Draw three tick marks.
|
||||
paint.setStrokeWidth(radius * 0.06f);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float angle = 180 + (i * 45); // 180°, 225°, 270°.
|
||||
float angleRad = (float) Math.toRadians(angle);
|
||||
|
||||
float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad);
|
||||
float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad);
|
||||
float endX = centerX + radius * (float) Math.cos(angleRad);
|
||||
float endY = centerY + radius * (float) Math.sin(angleRad);
|
||||
|
||||
canvas.drawLine(startX, startY, endX, endY, paint);
|
||||
}
|
||||
|
||||
// Draw needle.
|
||||
paint.setStrokeWidth(radius * 0.08f);
|
||||
float needleAngle = 200; // Slightly right of center.
|
||||
float needleAngleRad = (float) Math.toRadians(needleAngle);
|
||||
|
||||
float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad);
|
||||
float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad);
|
||||
|
||||
canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint);
|
||||
|
||||
// Center dot.
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawCircle(centerX, centerY, radius * 0.06f, paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return Utils.dipToPixels(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return Utils.dipToPixels(32);
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,10 @@ public interface VideoPlayer {
|
||||
long getCurrentPosition();
|
||||
|
||||
void seekTo(long positionMs);
|
||||
|
||||
void pause();
|
||||
|
||||
void play();
|
||||
|
||||
boolean isPlaying();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.amazon.video.sdk.player;
|
||||
|
||||
public interface Player {
|
||||
float getPlaybackRate();
|
||||
|
||||
void setPlaybackRate(float rate);
|
||||
|
||||
void play();
|
||||
|
||||
void pause();
|
||||
}
|
||||
@@ -1438,6 +1438,38 @@ public class Utils {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen height to actual device pixels.
|
||||
*
|
||||
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
|
||||
* @return The device pixel value corresponding to the percentage of screen height.
|
||||
*/
|
||||
public static int percentageHeightToPixels(int percentage) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (metrics.heightPixels * (percentage / 100.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage of the screen width to actual device pixels.
|
||||
*
|
||||
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
|
||||
* @return The device pixel value corresponding to the percentage of screen width.
|
||||
*/
|
||||
public static int percentageWidthToPixels(int percentage) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (metrics.widthPixels * (percentage / 100.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return isDarkModeEnabled()
|
||||
? adjustColorBrightness(baseColor, darkThemeFactor)
|
||||
: adjustColorBrightness(baseColor, lightThemeFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
|
||||
* <p>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -26,7 +24,7 @@ public class CustomDialogListPreference extends ListPreference {
|
||||
/**
|
||||
* Custom ArrayAdapter to handle checkmark visibility.
|
||||
*/
|
||||
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||
public static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||
private static class SubViewDataContainer {
|
||||
ImageView checkIcon;
|
||||
View placeholder;
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.graphics.drawable.shapes.RectShape;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import static app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference.showDialogIfAppIsNotInstalled;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.StringRef;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -36,7 +34,7 @@ public final class DownloadsPatch {
|
||||
*
|
||||
* Appears to always be called from the main thread.
|
||||
*/
|
||||
public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) {
|
||||
public static boolean inAppDownloadButtonOnClick(String videoId) {
|
||||
try {
|
||||
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
|
||||
return false;
|
||||
@@ -48,6 +46,9 @@ public final class DownloadsPatch {
|
||||
boolean isActivityContext = true;
|
||||
if (context == null) {
|
||||
// Utils context is the application context, and not an activity context.
|
||||
//
|
||||
// Edit: This check may no longer be needed since YT can now
|
||||
// only be launched from the main Activity (embedded usage in other apps no longer works).
|
||||
context = Utils.getContext();
|
||||
isActivityContext = false;
|
||||
}
|
||||
@@ -64,8 +65,7 @@ public final class DownloadsPatch {
|
||||
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
|
||||
* the downloader is opened as a new task (which forces YT to minimize).
|
||||
*/
|
||||
public static void launchExternalDownloader(@NonNull String videoId,
|
||||
@NonNull Context context, boolean isActivityContext) {
|
||||
public static void launchExternalDownloader(String videoId, Context context, boolean isActivityContext) {
|
||||
try {
|
||||
Objects.requireNonNull(videoId);
|
||||
Logger.printDebug(() -> "Launching external downloader with context: " + context);
|
||||
@@ -73,16 +73,8 @@ public final class DownloadsPatch {
|
||||
// Trim string to avoid any accidental whitespace.
|
||||
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
|
||||
|
||||
boolean packageEnabled = false;
|
||||
try {
|
||||
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
|
||||
} catch (PackageManager.NameNotFoundException error) {
|
||||
Logger.printDebug(() -> "External downloader could not be found: " + error);
|
||||
}
|
||||
|
||||
// If the package is not installed, show the toast
|
||||
if (!packageEnabled) {
|
||||
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
|
||||
// If the package is not installed, show a dialog.
|
||||
if (showDialogIfAppIsNotInstalled(context, downloaderPackageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,16 @@ public class ForceOriginalAudioPatch {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean ignoreDefaultAudioStream(boolean original) {
|
||||
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||
return false;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -50,7 +60,6 @@ public class ForceOriginalAudioPatch {
|
||||
return isOriginal;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||
|
||||
return isDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
|
||||
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@@ -55,7 +55,7 @@ public class ReturnYouTubeDislikePatch {
|
||||
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
||||
|
||||
/**
|
||||
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
|
||||
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilter}
|
||||
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
||||
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
||||
*/
|
||||
|
||||
@@ -6,8 +6,6 @@ import android.app.Instrumentation;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@@ -155,10 +153,10 @@ public final class AdsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == playerShoppingShelf) {
|
||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
|
||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (exceptions.matches(path)) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -21,7 +19,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
isVideoQualityMenuVisible = true;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -100,7 +98,7 @@ final class ButtonsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == likeSubscribeGlow) {
|
||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||
@@ -117,7 +115,7 @@ final class ButtonsFilter extends Filter {
|
||||
// Make sure the current path is the right one
|
||||
// to avoid false positives.
|
||||
return path.startsWith(VIDEO_ACTION_BAR_PATH)
|
||||
&& bufferButtonsGroupList.check(protobufBufferArray).isFiltered();
|
||||
&& bufferButtonsGroupList.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@@ -87,12 +85,12 @@ final class CommentsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == chipBar) {
|
||||
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
&& aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
||||
&& aiCommentsSummary.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -3,7 +3,6 @@ package app.revanced.extension.youtube.patches.components;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -146,7 +145,7 @@ final class CustomFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// All callbacks are custom filter groups.
|
||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||
@@ -158,6 +157,6 @@ final class CustomFilter extends Filter {
|
||||
return true; // No buffer filter, only path filtering.
|
||||
}
|
||||
|
||||
return custom.bufferSearch.matches(protobufBufferArray);
|
||||
return custom.bufferSearch.matches(buffer);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.StringTrieSearch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@@ -105,7 +103,7 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
|
||||
if (matchedGroup == aiGeneratedVideoSummarySection) {
|
||||
@@ -116,11 +114,11 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
if (exceptions.matches(path)) return false;
|
||||
|
||||
if (matchedGroup == macroMarkersCarousel) {
|
||||
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
|
||||
return contentIndex == 0 && macroMarkersCarouselGroupList.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == horizontalShelf) {
|
||||
return cellVideoAttribute.check(protobufBufferArray).isFiltered();
|
||||
return cellVideoAttribute.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -42,6 +40,7 @@ abstract class Filter {
|
||||
/**
|
||||
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* if any of the groups are found.
|
||||
* <p>
|
||||
*/
|
||||
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
||||
identifierCallbacks.addAll(Arrays.asList(groups));
|
||||
@@ -68,7 +67,7 @@ abstract class Filter {
|
||||
* @param contentIndex Matched index of the identifier or path.
|
||||
* @return True if the litho component should be filtered out.
|
||||
*/
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package app.revanced.extension.youtube.patches.components;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class HideInfoCardsFilterPatch extends Filter {
|
||||
public final class HideInfoCardsFilter extends Filter {
|
||||
|
||||
public HideInfoCardsFilterPatch() {
|
||||
public HideInfoCardsFilter() {
|
||||
addIdentifierCallbacks(
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_INFO_CARDS,
|
||||
@@ -554,7 +554,7 @@ final class KeywordContentFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||
return false;
|
||||
@@ -574,7 +574,7 @@ final class KeywordContentFilter extends Filter {
|
||||
}
|
||||
|
||||
MutableReference<String> matchRef = new MutableReference<>();
|
||||
if (bufferSearch.matches(protobufBufferArray, matchRef)) {
|
||||
if (bufferSearch.matches(buffer, matchRef)) {
|
||||
updateStats(true, matchRef.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
ticketShelf = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_TICKET_SHELF,
|
||||
"ticket.eml"
|
||||
"ticket_item.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
@@ -304,7 +304,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// This identifier is used not only in players but also in search results:
|
||||
// https://github.com/ReVanced/revanced-patches/issues/3245
|
||||
@@ -322,7 +322,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
}
|
||||
|
||||
if (matchedGroup == channelProfile) {
|
||||
return channelProfileBuffer.check(protobufBufferArray).isFiltered();
|
||||
return channelProfileBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||
@@ -331,11 +331,11 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
return compactChannelBarInnerButton.check(path).isFiltered()
|
||||
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
|
||||
// it's safe to assume that the button is the only thing that should be hidden.
|
||||
&& joinMembershipButton.check(protobufBufferArray).isFiltered();
|
||||
&& joinMembershipButton.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == horizontalShelves) {
|
||||
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
|
||||
return contentIndex == 0 && (hideShelves() || ticketShelf.check(buffer).isFiltered());
|
||||
}
|
||||
|
||||
if (matchedGroup == chipBar) {
|
||||
|
||||
@@ -17,29 +17,28 @@ public final class LithoFilterPatch {
|
||||
* Simple wrapper to pass the litho parameters through the prefix search.
|
||||
*/
|
||||
private static final class LithoFilterParameters {
|
||||
@Nullable
|
||||
final String identifier;
|
||||
final String path;
|
||||
final byte[] protoBuffer;
|
||||
final byte[] buffer;
|
||||
|
||||
LithoFilterParameters(@Nullable String lithoIdentifier, String lithoPath, byte[] protoBuffer) {
|
||||
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
|
||||
this.identifier = lithoIdentifier;
|
||||
this.path = lithoPath;
|
||||
this.protoBuffer = protoBuffer;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
// Estimate the percentage of the buffer that are Strings.
|
||||
StringBuilder builder = new StringBuilder(Math.max(100, protoBuffer.length / 2));
|
||||
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
||||
builder.append( "ID: ");
|
||||
builder.append(identifier);
|
||||
builder.append(" Path: ");
|
||||
builder.append(path);
|
||||
if (Settings.DEBUG_PROTOBUFFER.get()) {
|
||||
builder.append(" BufferStrings: ");
|
||||
findAsciiStrings(builder, protoBuffer);
|
||||
findAsciiStrings(builder, buffer);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
@@ -128,21 +127,21 @@ public final class LithoFilterPatch {
|
||||
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
|
||||
Filter filter, List<StringFilterGroup> groups,
|
||||
Filter.FilterContentType type) {
|
||||
String filterSimpleName = filter.getClass().getSimpleName();
|
||||
|
||||
for (StringFilterGroup group : groups) {
|
||||
if (!group.includeInSearch()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String pattern : group.filters) {
|
||||
String filterSimpleName = filter.getClass().getSimpleName();
|
||||
|
||||
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
|
||||
matchedLength, callbackParameter) -> {
|
||||
if (!group.isEnabled()) return false;
|
||||
|
||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||
parameters.path, parameters.protoBuffer, group, type, matchedStartIndex);
|
||||
parameters.path, parameters.buffer, group, type, matchedStartIndex);
|
||||
|
||||
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||
if (type == Filter.FilterContentType.IDENTIFIER) {
|
||||
@@ -163,6 +162,7 @@ public final class LithoFilterPatch {
|
||||
|
||||
/**
|
||||
* Injection point. Called off the main thread.
|
||||
* Targets 20.22+
|
||||
*/
|
||||
public static void setProtoBuffer(byte[] buffer) {
|
||||
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
||||
@@ -193,9 +193,9 @@ public final class LithoFilterPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean shouldFilter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
|
||||
try {
|
||||
if (pathBuilder.length() == 0) {
|
||||
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ public final class LithoFilterPatch {
|
||||
lithoIdentifier, pathBuilder.toString(), buffer);
|
||||
Logger.printDebug(() -> "Searching " + parameter);
|
||||
|
||||
if (parameter.identifier != null && identifierSearchTree.matches(parameter.identifier, parameter)) {
|
||||
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public final class LithoFilterPatch {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Litho filter failure", ex);
|
||||
Logger.printException(() -> "isFiltered failure", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
|
||||
*/
|
||||
public final class PlaybackSpeedMenuFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Old litho based speed selection menu.
|
||||
*/
|
||||
public static volatile boolean isOldPlaybackSpeedMenuVisible;
|
||||
|
||||
/**
|
||||
* 0.05x speed selection menu.
|
||||
*/
|
||||
public static volatile boolean isPlaybackRateSelectorMenuVisible;
|
||||
|
||||
private final StringFilterGroup oldPlaybackMenuGroup;
|
||||
|
||||
public PlaybackSpeedMenuFilter() {
|
||||
// 0.05x litho speed menu.
|
||||
var playbackRateSelectorGroup = new StringFilterGroup(
|
||||
Settings.CUSTOM_SPEED_MENU,
|
||||
"playback_rate_selector_menu_sheet.eml-js"
|
||||
);
|
||||
|
||||
// Old litho based speed menu.
|
||||
oldPlaybackMenuGroup = new StringFilterGroup(
|
||||
Settings.CUSTOM_SPEED_MENU,
|
||||
"playback_speed_sheet_content.eml-js");
|
||||
|
||||
addPathCallbacks(playbackRateSelectorGroup, oldPlaybackMenuGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == oldPlaybackMenuGroup) {
|
||||
isOldPlaybackSpeedMenuVisible = true;
|
||||
} else {
|
||||
isPlaybackRateSelectorMenuVisible = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
|
||||
*/
|
||||
public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
||||
|
||||
/**
|
||||
* 0.05x speed selection menu.
|
||||
*/
|
||||
public static volatile boolean isPlaybackRateSelectorMenuVisible;
|
||||
|
||||
public PlaybackSpeedMenuFilterPatch() {
|
||||
// 0.05x litho speed menu.
|
||||
var playbackRateSelectorGroup = new StringFilterGroup(
|
||||
Settings.CUSTOM_SPEED_MENU,
|
||||
"playback_rate_selector_menu_sheet.eml-js"
|
||||
);
|
||||
|
||||
addPathCallbacks(playbackRateSelectorGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
isPlaybackRateSelectorMenuVisible = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
@@ -96,7 +94,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == videoQualityMenuFooter) {
|
||||
return true;
|
||||
@@ -107,10 +105,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
}
|
||||
|
||||
// Shorts also use this player flyout panel
|
||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) {
|
||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(buffer).isFiltered()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return flyoutFilterGroupList.check(protobufBufferArray).isFiltered();
|
||||
return flyoutFilterGroupList.check(buffer).isFiltered();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import app.revanced.extension.youtube.TrieSearch;
|
||||
*
|
||||
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
||||
*/
|
||||
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
public final class ReturnYouTubeDislikeFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
||||
@@ -67,7 +67,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
|
||||
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
||||
|
||||
public ReturnYouTubeDislikeFilterPatch() {
|
||||
public ReturnYouTubeDislikeFilter() {
|
||||
// When a new Short is opened, the like buttons always seem to load before the dislike.
|
||||
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
|
||||
// So must check for both buttons.
|
||||
@@ -84,15 +84,15 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
|
||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer);
|
||||
if (result.isFiltered()) {
|
||||
String matchedVideoId = findVideoId(protobufBufferArray);
|
||||
String matchedVideoId = findVideoId(buffer);
|
||||
// Matched video will be null if in incognito mode.
|
||||
// Must pass a null id to correctly clear out the current video data.
|
||||
// Otherwise if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,
|
||||
@@ -4,8 +4,6 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -13,7 +11,6 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@@ -321,7 +318,7 @@ public final class ShortsFilter extends Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentType == FilterContentType.PATH) {
|
||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
|
||||
@@ -330,22 +327,22 @@ public final class ShortsFilter extends Filter {
|
||||
}
|
||||
|
||||
if (matchedGroup == useSoundButton) {
|
||||
return useSoundButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||
return useSoundButtonBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == useTemplateButton) {
|
||||
return useTemplateButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||
return useTemplateButtonBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == shortsCompactFeedVideo) {
|
||||
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
|
||||
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
// Video action buttons (comment, share, remix) have the same path.
|
||||
// Like and dislike are separate path filters and don't require buffer searching.
|
||||
if (matchedGroup == shortsActionBar) {
|
||||
return videoActionButton.check(path).isFiltered()
|
||||
&& videoActionButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||
&& videoActionButtonBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == suggestedAction) {
|
||||
@@ -356,7 +353,7 @@ public final class ShortsFilter extends Filter {
|
||||
return true;
|
||||
}
|
||||
|
||||
return suggestedActionsBuffer.check(protobufBufferArray).isFiltered();
|
||||
return suggestedActionsBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -18,7 +18,7 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
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 final class AdvancedVideoQualityMenuPatch {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 final class AdvancedVideoQualityMenuPatch {
|
||||
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 final class AdvancedVideoQualityMenuPatch {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,9 @@ import static app.revanced.extension.shared.Utils.NetworkType;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
@@ -17,10 +16,29 @@ import app.revanced.extension.shared.settings.IntegerSetting;
|
||||
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 {
|
||||
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
|
||||
|
||||
/**
|
||||
* Interface to use obfuscated methods.
|
||||
*/
|
||||
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 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_PREMIUM_NAME = "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;
|
||||
@@ -29,46 +47,83 @@ public class RememberVideoQualityPatch {
|
||||
private static boolean qualityNeedsUpdating;
|
||||
|
||||
/**
|
||||
* If the user selected a new quality from the flyout menu,
|
||||
* and {@link Settings#REMEMBER_VIDEO_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 in human readable form: [1080, 720, 480]
|
||||
* The available qualities of the current video.
|
||||
*/
|
||||
@Nullable
|
||||
private static List<Integer> videoQualities;
|
||||
private static VideoQuality[] currentQualities;
|
||||
|
||||
private static boolean shouldRememberVideoQuality() {
|
||||
BooleanSetting preference = ShortsPlayerState.isOpen() ?
|
||||
Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
|
||||
/**
|
||||
* The current quality of the video playing.
|
||||
* This is always the actual quality even if Automatic quality is active.
|
||||
*/
|
||||
@Nullable
|
||||
private static VideoQuality currentQuality;
|
||||
|
||||
/**
|
||||
* The current VideoQualityMenuInterface, set during setVideoQuality.
|
||||
*/
|
||||
@Nullable
|
||||
private static VideoQualityMenuInterface currentMenuInterface;
|
||||
|
||||
@Nullable
|
||||
public static VideoQuality[] getCurrentQualities() {
|
||||
return currentQualities;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VideoQuality getCurrentQuality() {
|
||||
return currentQuality;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VideoQualityMenuInterface getCurrentMenuInterface() {
|
||||
return currentMenuInterface;
|
||||
}
|
||||
|
||||
public static boolean shouldRememberVideoQuality() {
|
||||
BooleanSetting preference = ShortsPlayerState.isOpen()
|
||||
? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
|
||||
: Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED;
|
||||
return preference.get();
|
||||
}
|
||||
|
||||
private static void changeDefaultQuality(int defaultQuality) {
|
||||
public static int getDefaultQualityResolution() {
|
||||
final boolean isShorts = ShortsPlayerState.isOpen();
|
||||
IntegerSetting preference = Utils.getNetworkType() == NetworkType.MOBILE
|
||||
? (isShorts ? shortsQualityMobile : videoQualityMobile)
|
||||
: (isShorts ? shortsQualityWifi : videoQualityWifi);
|
||||
return preference.get();
|
||||
}
|
||||
|
||||
public static void saveDefaultQuality(int qualityResolution) {
|
||||
final boolean shortPlayerOpen = ShortsPlayerState.isOpen();
|
||||
String networkTypeMessage;
|
||||
boolean useShortsPreference = ShortsPlayerState.isOpen();
|
||||
IntegerSetting qualitySetting;
|
||||
if (Utils.getNetworkType() == NetworkType.MOBILE) {
|
||||
if (useShortsPreference) shortsQualityMobile.save(defaultQuality);
|
||||
else videoQualityMobile.save(defaultQuality);
|
||||
networkTypeMessage = str("revanced_remember_video_quality_mobile");
|
||||
qualitySetting = shortPlayerOpen ? shortsQualityMobile : videoQualityMobile;
|
||||
} else {
|
||||
if (useShortsPreference) shortsQualityWifi.save(defaultQuality);
|
||||
else videoQualityWifi.save(defaultQuality);
|
||||
networkTypeMessage = str("revanced_remember_video_quality_wifi");
|
||||
qualitySetting = shortPlayerOpen ? shortsQualityWifi : videoQualityWifi;
|
||||
}
|
||||
if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get())
|
||||
|
||||
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()) {
|
||||
String qualityLabel = qualityResolution + "p";
|
||||
Utils.showToastShort(str(
|
||||
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
|
||||
networkTypeMessage, (defaultQuality + "p")
|
||||
));
|
||||
shortPlayerOpen
|
||||
? "revanced_remember_video_quality_toast_shorts"
|
||||
: "revanced_remember_video_quality_toast",
|
||||
networkTypeMessage,
|
||||
qualityLabel)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,109 +132,132 @@ public class RememberVideoQualityPatch {
|
||||
* @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(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) {
|
||||
try {
|
||||
boolean useShortsPreference = ShortsPlayerState.isOpen();
|
||||
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
|
||||
? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
|
||||
: (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
|
||||
Utils.verifyOnMainThread();
|
||||
currentMenuInterface = menu;
|
||||
|
||||
if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
||||
final boolean availableQualitiesChanged = (currentQualities == null)
|
||||
|| !Arrays.equals(currentQualities, qualities);
|
||||
if (availableQualitiesChanged) {
|
||||
currentQualities = qualities;
|
||||
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
|
||||
}
|
||||
|
||||
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
||||
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
|
||||
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
|
||||
currentQuality = updatedCurrentQuality;
|
||||
Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality);
|
||||
|
||||
VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality);
|
||||
}
|
||||
|
||||
final int preferredQuality = getDefaultQualityResolution();
|
||||
if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
||||
return originalQualityIndex; // Nothing to do.
|
||||
}
|
||||
|
||||
if (videoQualities == null || videoQualities.size() != qualities.length) {
|
||||
videoQualities = new ArrayList<>(qualities.length);
|
||||
for (Object streamQuality : qualities) {
|
||||
for (Field field : streamQuality.getClass().getFields()) {
|
||||
if (field.getType().isAssignableFrom(Integer.TYPE)
|
||||
&& field.getName().length() <= 2) {
|
||||
videoQualities.add(field.getInt(streamQuality));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
userChangedDefaultQuality = false;
|
||||
final int quality = videoQualities.get(userSelectedQualityIndex);
|
||||
Logger.printDebug(() -> "User changed default quality to: " + quality);
|
||||
changeDefaultQuality(quality);
|
||||
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 && !availableQualitiesChanged) {
|
||||
return originalQualityIndex;
|
||||
}
|
||||
qualityNeedsUpdating = false;
|
||||
|
||||
// Find the highest quality that is equal to or less than the preferred.
|
||||
int qualityToUse = videoQualities.get(0); // first element is automatic mode
|
||||
int qualityIndexToUse = 0;
|
||||
int i = 0;
|
||||
for (Integer quality : videoQualities) {
|
||||
if (quality <= preferredQuality && qualityToUse < quality) {
|
||||
qualityToUse = quality;
|
||||
qualityIndexToUse = i;
|
||||
for (VideoQuality quality : qualities) {
|
||||
final int qualityResolution = quality.patch_getResolution();
|
||||
if ((qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality)
|
||||
// Use the lowest video quality if the default is lower than all available.
|
||||
|| i == qualities.length - 1) {
|
||||
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 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".
|
||||
//
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// If the desired quality index is equal to the original index,
|
||||
// then the video is already set to the desired default quality.
|
||||
final int qualityToUseFinal = qualityToUse;
|
||||
if (qualityIndexToUse == originalQualityIndex) {
|
||||
// On first load of a new video, if the UI video quality flyout menu
|
||||
// is not updated then it will still show 'Auto' (ie: Auto (480p)),
|
||||
// even though it's already set to the desired resolution.
|
||||
//
|
||||
// To prevent confusion, set the video index anyways (even if it matches the existing index)
|
||||
// as that will force the UI picker to not display "Auto".
|
||||
Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Changing video quality from: "
|
||||
+ videoQualities.get(originalQualityIndex) + " to: " + qualityToUseFinal);
|
||||
}
|
||||
|
||||
Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE);
|
||||
m.invoke(qInterface, qualityToUse);
|
||||
return qualityIndexToUse;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to set quality", ex);
|
||||
return originalQualityIndex;
|
||||
Logger.printException(() -> "setVideoQuality failure", ex);
|
||||
}
|
||||
return originalQualityIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* @param userSelectedQualityIndex Element index of {@link #currentQualities}.
|
||||
*/
|
||||
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[userSelectedQualityIndex];
|
||||
saveDefaultQuality(quality.patch_getResolution());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "userChangedShortsQuality failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Old quality menu.
|
||||
* Injection point. Regular videos.
|
||||
* @param videoResolution Human readable resolution: 480, 720, 1080.
|
||||
*/
|
||||
public static void userChangedQuality(int selectedQualityIndex) {
|
||||
public static void userChangedQuality(int videoResolution) {
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
if (shouldRememberVideoQuality()) {
|
||||
userSelectedQualityIndex = selectedQualityIndex;
|
||||
userChangedDefaultQuality = true;
|
||||
saveDefaultQuality(videoResolution);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. New quality menu.
|
||||
*/
|
||||
public static void userChangedQualityInNewFlyout(int selectedQuality) {
|
||||
if (!shouldRememberVideoQuality()) return;
|
||||
|
||||
changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
Logger.printDebug(() -> "newVideoStarted");
|
||||
currentQualities = null;
|
||||
currentQuality = null;
|
||||
currentMenuInterface = null;
|
||||
qualityNeedsUpdating = true;
|
||||
videoQualities = null;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
@@ -42,7 +41,7 @@ import java.util.function.Function;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
|
||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import kotlin.Unit;
|
||||
@@ -80,6 +79,16 @@ public class CustomPlaybackSpeedPatch {
|
||||
*/
|
||||
public static final float[] customPlaybackSpeeds;
|
||||
|
||||
/**
|
||||
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
|
||||
*/
|
||||
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
|
||||
|
||||
/**
|
||||
* The last time the old playback menu was forcefully called.
|
||||
*/
|
||||
private static volatile long lastTimeOldPlaybackMenuInvoked;
|
||||
|
||||
/**
|
||||
* Formats speeds to UI strings.
|
||||
*/
|
||||
@@ -90,11 +99,6 @@ public class CustomPlaybackSpeedPatch {
|
||||
*/
|
||||
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
|
||||
*/
|
||||
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
|
||||
|
||||
static {
|
||||
// Cap at 2 decimals (rounds automatically).
|
||||
speedFormatter.setMaximumFractionDigits(2);
|
||||
@@ -174,25 +178,33 @@ public class CustomPlaybackSpeedPatch {
|
||||
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
|
||||
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
|
||||
try {
|
||||
if (PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible) {
|
||||
if (hideLithoMenuAndShowCustomSpeedMenu(recyclerView, 5)) {
|
||||
PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible = false;
|
||||
if (PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible) {
|
||||
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 5)) {
|
||||
PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
|
||||
Logger.printException(() -> "isPlaybackRateSelectorMenuVisible failure", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
if (PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible) {
|
||||
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 8)) {
|
||||
PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "isOldPlaybackSpeedMenuVisible failure", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static boolean hideLithoMenuAndShowCustomSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
|
||||
private static boolean hideLithoMenuAndShowSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
|
||||
if (recyclerView.getChildCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
View firstChild = recyclerView.getChildAt(0);
|
||||
if (!(firstChild instanceof ViewGroup playbackSpeedParentView)) {
|
||||
if (!(recyclerView.getChildAt(0) instanceof ViewGroup playbackSpeedParentView)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -200,33 +212,49 @@ public class CustomPlaybackSpeedPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
ViewParent parentView3rd = Utils.getParentView(recyclerView, 3);
|
||||
if (!(parentView3rd instanceof ViewGroup)) {
|
||||
return true;
|
||||
if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ViewParent parentView4th = parentView3rd.getParent();
|
||||
if (!(parentView4th instanceof ViewGroup)) {
|
||||
return true;
|
||||
if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
|
||||
// This only shows in phone layout.
|
||||
final var touchInsidedView = ((ViewGroup) parentView4th).getChildAt(0);
|
||||
var touchInsidedView = parentView4th.getChildAt(0);
|
||||
touchInsidedView.setSoundEffectsEnabled(false);
|
||||
touchInsidedView.performClick();
|
||||
|
||||
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
|
||||
((ViewGroup) parentView3rd).setVisibility(View.GONE);
|
||||
((ViewGroup) parentView4th).setVisibility(View.GONE);
|
||||
parentView3rd.setVisibility(View.GONE);
|
||||
parentView4th.setVisibility(View.GONE);
|
||||
|
||||
// Close the litho speed menu and show the modern custom speed dialog.
|
||||
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
|
||||
Logger.printDebug(() -> "Modern playback speed dialog shown");
|
||||
// Close the litho speed menu and show the custom speeds.
|
||||
if (Settings.RESTORE_OLD_SPEED_MENU.get()) {
|
||||
showOldPlaybackSpeedMenu();
|
||||
Logger.printDebug(() -> "Old playback speed dialog shown");
|
||||
} else {
|
||||
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
|
||||
Logger.printDebug(() -> "Modern playback speed dialog shown");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void showOldPlaybackSpeedMenu() {
|
||||
// This method is sometimes used multiple times.
|
||||
// To prevent this, ignore method reuse within 1 second.
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastTimeOldPlaybackMenuInvoked < 1000) {
|
||||
Logger.printDebug(() -> "Ignoring call to showOldPlaybackSpeedMenu");
|
||||
return;
|
||||
}
|
||||
lastTimeOldPlaybackMenuInvoked = now;
|
||||
|
||||
// Rest of the implementation added by patch.
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a modern custom dialog for adjusting video playback speed.
|
||||
* <p>
|
||||
@@ -643,11 +671,9 @@ public class CustomPlaybackSpeedPatch {
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
@@ -171,4 +172,10 @@ public class LicenseActivityHook extends Activity {
|
||||
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
|
||||
if (searchViewController != null) {
|
||||
searchViewController.handleOrientationChange(newConfig.orientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ public class SearchViewController {
|
||||
this.originalTitle = toolbar.getTitle();
|
||||
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
|
||||
this.searchHistory = new LinkedList<>();
|
||||
this.currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
|
||||
if (showSettingsSearchHistory) {
|
||||
String entries = searchEntries.get();
|
||||
@@ -208,8 +209,6 @@ public class SearchViewController {
|
||||
Logger.printException(() -> "navigation click failure", ex);
|
||||
}
|
||||
});
|
||||
|
||||
monitorOrientationChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,19 +291,14 @@ public class SearchViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorOrientationChanges() {
|
||||
currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||
|
||||
searchView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
int newOrientation = activity.getResources().getConfiguration().orientation;
|
||||
if (newOrientation != currentOrientation) {
|
||||
currentOrientation = newOrientation;
|
||||
if (autoCompleteTextView != null) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
Logger.printDebug(() -> "Orientation changed, search history dismissed");
|
||||
}
|
||||
public void handleOrientationChange(int newOrientation) {
|
||||
if (newOrientation != currentOrientation) {
|
||||
currentOrientation = newOrientation;
|
||||
if (autoCompleteTextView != null) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
Logger.printDebug(() -> "Orientation changed, search history dismissed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,14 +344,14 @@ public class SearchViewController {
|
||||
|
||||
public static boolean handleBackPress() {
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchExpanded()) {
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
LicenseActivityHook.searchViewController.closeSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSearchExpanded() {
|
||||
public boolean isSearchActive() {
|
||||
return isSearchActive;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,9 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
|
||||
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, false,
|
||||
parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED));
|
||||
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
||||
public static final BooleanSetting RESTORE_OLD_SPEED_MENU = new BooleanSetting("revanced_restore_old_speed_menu", FALSE, parent(CUSTOM_SPEED_MENU));
|
||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
||||
|
||||
@@ -171,6 +172,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);
|
||||
|
||||
@@ -191,7 +193,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
"com.deniscerri.ytdl" /* YTDLnis */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
|
||||
// Comments
|
||||
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
|
||||
|
||||
@@ -16,10 +16,8 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
|
||||
|
||||
/**
|
||||
* Initialize a settings preference list with the available playback speeds.
|
||||
*/
|
||||
private void initializeEntryValues() {
|
||||
{
|
||||
// Initialize a settings preference list with the available playback speeds.
|
||||
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
|
||||
final int numberOfEntries = customPlaybackSpeeds.length + 1;
|
||||
String[] preferenceListEntries = new String[numberOfEntries];
|
||||
@@ -41,10 +39,6 @@ public final class CustomVideoSpeedListPreference extends CustomDialogListPrefer
|
||||
setEntryValues(preferenceListEntryValues);
|
||||
}
|
||||
|
||||
{
|
||||
initializeEntryValues();
|
||||
}
|
||||
|
||||
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.sf;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* A custom ListPreference for selecting an external downloader package with checkmarks and EditText for custom package names.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
|
||||
/**
|
||||
* Enum representing supported external downloaders with their display names, package names, and download URLs.
|
||||
*/
|
||||
private enum Downloader {
|
||||
YTDLNIS("YTDLnis",
|
||||
"com.deniscerri.ytdl",
|
||||
"https://ytdlnis.org",
|
||||
true),
|
||||
SEAL("Seal",
|
||||
"com.junkfood.seal",
|
||||
"https://github.com/JunkFood02/Seal/releases/latest",
|
||||
true),
|
||||
GRAYJAY("Grayjay",
|
||||
"com.futo.platformplayer",
|
||||
"https://grayjay.app"),
|
||||
LIBRETUBE("LibreTube",
|
||||
"com.github.libretube",
|
||||
"https://libretube.dev"),
|
||||
NEWPIPE("NewPipe",
|
||||
"org.schabi.newpipe",
|
||||
"https://newpipe.net"),
|
||||
PIPEPIPE("PipePipe",
|
||||
"InfinityLoop1309.NewPipeEnhanced",
|
||||
"https://pipepipe.dev"),
|
||||
TUBULAR("Tubular",
|
||||
"org.polymorphicshade.tubular",
|
||||
"https://github.com/polymorphicshade/Tubular/releases/latest"),
|
||||
OTHER(sf("revanced_external_downloader_other_item").toString(),
|
||||
null,
|
||||
null,
|
||||
true);
|
||||
|
||||
private static final Map<String, Downloader> PACKAGE_TO_ENUM = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Downloader downloader : values()) {
|
||||
String packageName = downloader.packageName;
|
||||
if (packageName != null) {
|
||||
PACKAGE_TO_ENUM.put(packageName, downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a Downloader by its package name. This method can never return {@link #OTHER}.
|
||||
* @return The Downloader enum or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public static Downloader findByPackageName(String packageName) {
|
||||
return PACKAGE_TO_ENUM.get(Objects.requireNonNull(packageName));
|
||||
}
|
||||
|
||||
public final String name;
|
||||
@Nullable
|
||||
public final String packageName;
|
||||
@Nullable
|
||||
public final String downloadUrl;
|
||||
/**
|
||||
* If a downloader app should be shown in the preference settings
|
||||
* if the app is not currently installed.
|
||||
*/
|
||||
public final boolean isPreferred;
|
||||
|
||||
Downloader(String name, String packageName, String downloadUrl) {
|
||||
this(name, packageName, downloadUrl, false);
|
||||
}
|
||||
|
||||
Downloader(String name, @Nullable String packageName, @Nullable String downloadUrl, boolean isPreferred) {
|
||||
this.name = name;
|
||||
this.packageName = packageName;
|
||||
this.downloadUrl = downloadUrl;
|
||||
this.isPreferred = isPreferred;
|
||||
}
|
||||
|
||||
public boolean isInstalled() {
|
||||
return packageName != null && isAppInstalledAndEnabled(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAppInstalledAndEnabled(String packageName) {
|
||||
try {
|
||||
if (Utils.getContext().getPackageManager().getApplicationInfo(packageName, 0).enabled) {
|
||||
Logger.printDebug(() -> "App installed: " + packageName);
|
||||
return true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException error) {
|
||||
Logger.printDebug(() -> "App not installed: " + packageName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private EditText editText;
|
||||
private CustomDialogListPreference.ListPreferenceArrayAdapter adapter;
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ExternalDownloaderPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private void updateEntries() {
|
||||
List<CharSequence> entries = new ArrayList<>();
|
||||
List<CharSequence> entryValues = new ArrayList<>();
|
||||
|
||||
for (Downloader downloader : Downloader.values()) {
|
||||
if (downloader.isPreferred || downloader.isInstalled()) {
|
||||
String packageName = downloader.packageName;
|
||||
|
||||
entries.add(downloader.name);
|
||||
entryValues.add(packageName != null
|
||||
? packageName
|
||||
: Downloader.OTHER.name);
|
||||
}
|
||||
}
|
||||
|
||||
setEntries(entries.toArray(new CharSequence[0]));
|
||||
setEntryValues(entryValues.toArray(new CharSequence[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the summary for this ListPreference.
|
||||
*/
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
// Ignore calls to set the summary.
|
||||
// Summary is always the description of the category.
|
||||
//
|
||||
// This is required otherwise the ReVanced preference fragment
|
||||
// sets all ListPreference summaries to show the current selection.
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a custom dialog with a ListView for predefined downloader packages and EditText for custom package input.
|
||||
*/
|
||||
@Override
|
||||
protected void showDialog(@Nullable Bundle state) {
|
||||
// Must set entries before showing the dialog, to handle if
|
||||
// an app is installed while the settings are open in the background.
|
||||
updateEntries();
|
||||
|
||||
Context context = getContext();
|
||||
String packageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||
|
||||
// Create the main layout for the dialog content.
|
||||
LinearLayout contentLayout = new LinearLayout(context);
|
||||
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
// Create ListView for predefined downloader apps.
|
||||
ListView listView = new ListView(context);
|
||||
listView.setId(android.R.id.list);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
// Create custom adapter for the ListView.
|
||||
final boolean usingCustomDownloader = Downloader.findByPackageName(packageName) == null;
|
||||
adapter = new CustomDialogListPreference.ListPreferenceArrayAdapter(
|
||||
context,
|
||||
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
|
||||
getEntries(),
|
||||
getEntryValues(),
|
||||
usingCustomDownloader
|
||||
? Downloader.OTHER.name
|
||||
: packageName
|
||||
);
|
||||
listView.setAdapter(adapter);
|
||||
|
||||
Function<String, Void> updateListViewSelection = (updatedPackageName) -> {
|
||||
String entryValueName = Downloader.findByPackageName(updatedPackageName) == null
|
||||
? Downloader.OTHER.name
|
||||
: updatedPackageName;
|
||||
CharSequence[] entryValues = getEntryValues();
|
||||
|
||||
for (int i = 0, length = entryValues.length; i < length; i++) {
|
||||
String entryString = entryValues[i].toString();
|
||||
if (entryString.equals(entryValueName)) {
|
||||
listView.setItemChecked(i, true);
|
||||
listView.setSelection(i);
|
||||
adapter.setSelectedValue(entryString);
|
||||
adapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
updateListViewSelection.apply(packageName);
|
||||
|
||||
// Handle item click to select value.
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
String selectedValue = getEntryValues()[position].toString();
|
||||
Downloader selectedApp = Downloader.findByPackageName(selectedValue);
|
||||
|
||||
if (selectedApp != null) {
|
||||
editText.setText(selectedApp.packageName);
|
||||
editText.setEnabled(false); // Disable editing for predefined options.
|
||||
} else {
|
||||
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
|
||||
editText.setText(Downloader.findByPackageName(savedPackageName) == null
|
||||
? savedPackageName // If the user is clicking thru options then retain existing other app.
|
||||
: ""
|
||||
);
|
||||
editText.setEnabled(true); // Enable editing for Custom.
|
||||
editText.requestFocus();
|
||||
}
|
||||
editText.setSelection(editText.getText().length());
|
||||
adapter.setSelectedValue(selectedValue);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
// Add ListView to content layout with initial height.
|
||||
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
0 // Initial height, will be updated.
|
||||
);
|
||||
listViewParams.bottomMargin = dipToPixels(16);
|
||||
contentLayout.addView(listView, listViewParams);
|
||||
|
||||
// Add EditText for custom package name.
|
||||
editText = new EditText(context);
|
||||
editText.setText(packageName);
|
||||
editText.setSelection(packageName.length());
|
||||
editText.setHint(str("revanced_external_downloader_other_item_hint"));
|
||||
editText.setSingleLine(true); // Restrict EditText to a single line.
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||
// Set initial EditText state based on selected downloader.
|
||||
editText.setEnabled(usingCustomDownloader);
|
||||
editText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
String updatedPackageName = edit.toString().trim();
|
||||
updateListViewSelection.apply(updatedPackageName);
|
||||
}
|
||||
});
|
||||
|
||||
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(10), null, null));
|
||||
editTextBackground.getPaint().setColor(Utils.getEditTextBackground());
|
||||
final int dip8 = dipToPixels(8);
|
||||
editText.setPadding(dip8, dip8, dip8, dip8);
|
||||
editText.setBackground(editTextBackground);
|
||||
editText.setClipToOutline(true);
|
||||
contentLayout.addView(editText);
|
||||
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
context,
|
||||
getTitle() != null ? getTitle().toString() : "",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
() -> {
|
||||
String newValue = editText.getText().toString().trim();
|
||||
if (newValue.isEmpty()) {
|
||||
// Show dialog if EditText is empty.
|
||||
Utils.createCustomDialog(
|
||||
context,
|
||||
str("revanced_external_downloader_name_title"),
|
||||
str("revanced_external_downloader_empty_warning"),
|
||||
null,
|
||||
null,
|
||||
() -> {}, // OK button does nothing (dismiss only).
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false
|
||||
).first.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (showDialogIfAppIsNotInstalled(getContext(), newValue)) {
|
||||
return; // Invalid package. Do not save.
|
||||
}
|
||||
|
||||
// Save custom package name.
|
||||
if (callChangeListener(newValue)) {
|
||||
setValue(newValue);
|
||||
}
|
||||
},
|
||||
() -> {}, // Cancel button action (dismiss only).
|
||||
str("revanced_settings_reset"),
|
||||
() -> { // Reset action.
|
||||
String defaultValue = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.defaultValue;
|
||||
editText.setText(defaultValue);
|
||||
editText.setSelection(defaultValue.length());
|
||||
editText.setEnabled(false); // Disable editing on reset.
|
||||
updateListViewSelection.apply(defaultValue);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Add the content layout directly to the dialog's main layout.
|
||||
LinearLayout dialogMainLayout = dialogPair.second;
|
||||
dialogMainLayout.addView(contentLayout, dialogMainLayout.getChildCount() - 1);
|
||||
|
||||
// Update ListView height dynamically based on orientation.
|
||||
//noinspection ExtractMethodRecommender
|
||||
Runnable updateListViewHeight = () -> {
|
||||
int totalHeight = 0;
|
||||
ListAdapter listAdapter = listView.getAdapter();
|
||||
if (listAdapter != null) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
final int listAdapterCount = listAdapter.getCount();
|
||||
for (int i = 0; i < listAdapterCount; i++) {
|
||||
View item = listAdapter.getView(i, null, listView);
|
||||
item.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.AT_MOST),
|
||||
View.MeasureSpec.UNSPECIFIED
|
||||
);
|
||||
totalHeight += item.getMeasuredHeight();
|
||||
}
|
||||
totalHeight += listView.getDividerHeight() * (listAdapterCount - 1);
|
||||
}
|
||||
|
||||
final int orientation = context.getResources().getConfiguration().orientation;
|
||||
if (orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
|
||||
// In portrait orientation, use WRAP_CONTENT for ListView height.
|
||||
listViewParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
|
||||
} else {
|
||||
// In landscape orientation, limit ListView height to 30% of screen height.
|
||||
final int maxHeight = Utils.percentageHeightToPixels(30);
|
||||
listViewParams.height = Math.min(totalHeight, maxHeight);
|
||||
}
|
||||
listView.setLayoutParams(listViewParams);
|
||||
};
|
||||
|
||||
// Initial height calculation.
|
||||
updateListViewHeight.run();
|
||||
|
||||
// Listen for configuration changes (e.g., orientation).
|
||||
View dialogView = dialogPair.second;
|
||||
// Recalculate height when layout changes (e.g., orientation change).
|
||||
dialogView.getViewTreeObserver().addOnGlobalLayoutListener(updateListViewHeight::run);
|
||||
|
||||
// Show the dialog.
|
||||
dialogPair.first.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the app is not installed and a dialog was shown.
|
||||
*/
|
||||
public static boolean showDialogIfAppIsNotInstalled(Context context, String packageName) {
|
||||
if (isAppInstalledAndEnabled(packageName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Downloader downloader = Downloader.findByPackageName(packageName);
|
||||
String downloadUrl = downloader != null
|
||||
? downloader.downloadUrl
|
||||
: null;
|
||||
String okButtonText = downloadUrl != null
|
||||
? str("gms_core_dialog_open_website_text") // Open website.
|
||||
: null; // Ok.
|
||||
// Show a dialog if the recommended app is not installed or if the custom package cannot be found.
|
||||
String message = downloader != null
|
||||
? str("revanced_external_downloader_not_installed_warning", downloader.name)
|
||||
: str("revanced_external_downloader_package_not_found_warning", packageName);
|
||||
|
||||
Utils.createCustomDialog(
|
||||
context,
|
||||
str("revanced_external_downloader_not_found_title"),
|
||||
message,
|
||||
null,
|
||||
okButtonText,
|
||||
() -> {
|
||||
try {
|
||||
// OK button action: open the downloader's URL if available.
|
||||
if (downloadUrl != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to open downloader URL: " + downloader, ex);
|
||||
}
|
||||
},
|
||||
() -> {}, // Cancel button action (dismiss only).
|
||||
null,
|
||||
null,
|
||||
false
|
||||
).first.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchExpanded()) {
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
|
||||
@@ -333,10 +333,8 @@ class AbstractPreferenceSearchData<T extends Preference> {
|
||||
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);
|
||||
|
||||
@@ -830,11 +830,10 @@ public class SegmentPlaybackController {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
params.y = dipToPixels(72);
|
||||
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
|
||||
int portraitWidth = Utils.percentageWidthToPixels(60); // 60% of the screen width.
|
||||
|
||||
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
|
||||
portraitWidth = Math.min(portraitWidth, Utils.percentageHeightToPixels(60)); // 60% of the screen height.
|
||||
}
|
||||
params.width = portraitWidth;
|
||||
params.dimAmount = 0.0f;
|
||||
|
||||
@@ -9,9 +9,6 @@ import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.showToastShort;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PlaybackSpeedDialogButton {
|
||||
@Nullable
|
||||
@@ -29,7 +26,11 @@ public class PlaybackSpeedDialogButton {
|
||||
Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get,
|
||||
view -> {
|
||||
try {
|
||||
CustomPlaybackSpeedPatch.showModernCustomPlaybackSpeedDialog(view.getContext());
|
||||
if (Settings.RESTORE_OLD_SPEED_MENU.get()) {
|
||||
CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu();
|
||||
} else {
|
||||
CustomPlaybackSpeedPatch.showModernCustomPlaybackSpeedDialog(view.getContext());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "speed button onClick failure", ex);
|
||||
}
|
||||
|
||||
@@ -187,4 +187,56 @@ public class PlayerControlButton {
|
||||
if (view != null) view.setVisibility(View.GONE);
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon of the button.
|
||||
* @param resourceId Drawable identifier, or zero to hide the icon.
|
||||
*/
|
||||
public void setIcon(int resourceId) {
|
||||
try {
|
||||
View button = buttonRef.get();
|
||||
if (button instanceof ImageView imageButton) {
|
||||
imageButton.setImageResource(resourceId);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
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 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;
|
||||
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.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;
|
||||
|
||||
@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");
|
||||
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 = getDrawableIdentifier("revanced_video_quality_dialog_button_unknown");
|
||||
|
||||
@Nullable
|
||||
private static PlayerControlButton instance;
|
||||
|
||||
/**
|
||||
* The current resource name of the button icon.
|
||||
*/
|
||||
private static int currentIconResource;
|
||||
|
||||
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) {
|
||||
try {
|
||||
Utils.verifyOnMainThread();
|
||||
if (instance == null) return;
|
||||
|
||||
final int resolution = quality == null
|
||||
? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading.
|
||||
: quality.patch_getResolution();
|
||||
|
||||
final int iconResource = switch (resolution) {
|
||||
case 144, 240, 360 -> DRAWABLE_LD;
|
||||
case 480 -> DRAWABLE_SD;
|
||||
case 720 -> DRAWABLE_HD;
|
||||
case 1080 -> VIDEO_QUALITY_1080P_PREMIUM_NAME.equals(quality.patch_getQualityName())
|
||||
? DRAWABLE_FHD_PLUS
|
||||
: DRAWABLE_FHD;
|
||||
case 1440 -> DRAWABLE_QHD;
|
||||
case 2160 -> DRAWABLE_4K;
|
||||
default -> DRAWABLE_UNKNOWN;
|
||||
};
|
||||
|
||||
if (iconResource != currentIconResource) {
|
||||
currentIconResource = 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
showVideoQualityDialog(view.getContext());
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Video quality button onClick failure", ex);
|
||||
}
|
||||
},
|
||||
view -> {
|
||||
try {
|
||||
VideoQuality[] qualities = RememberVideoQualityPatch.getCurrentQualities();
|
||||
VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface();
|
||||
if (qualities == null || menu == null) {
|
||||
Logger.printDebug(() -> "Cannot reset quality, videoQualities is null");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reset to default quality.
|
||||
final int defaultResolution = RememberVideoQualityPatch.getDefaultQualityResolution();
|
||||
for (VideoQuality quality : qualities) {
|
||||
final int resolution = quality.patch_getResolution();
|
||||
if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) {
|
||||
Logger.printDebug(() -> "Resetting quality to: " + quality);
|
||||
menu.patch_setQuality(quality);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Set initial icon.
|
||||
updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality());
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initializeButton failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setVisibilityImmediate(boolean visible) {
|
||||
if (instance != null) {
|
||||
instance.setVisibilityImmediate(visible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setVisibility(boolean visible, boolean animated) {
|
||||
if (instance != null) {
|
||||
instance.setVisibility(visible, animated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality.
|
||||
*/
|
||||
private static void showVideoQualityDialog(Context context) {
|
||||
try {
|
||||
VideoQuality[] currentQualities = RememberVideoQualityPatch.getCurrentQualities();
|
||||
VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality();
|
||||
if (currentQualities == null || currentQuality == null) {
|
||||
Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null");
|
||||
return;
|
||||
}
|
||||
if (currentQualities.length < 2) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Cannot show qualities dialog, no qualities available");
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
int listViewSelectedIndex = -1;
|
||||
for (VideoQuality quality : currentQualities) {
|
||||
if (quality.patch_getQualityName().equals(currentQuality.patch_getQualityName())) {
|
||||
break;
|
||||
}
|
||||
listViewSelectedIndex++;
|
||||
}
|
||||
|
||||
List<String> qualityLabels = new ArrayList<>(currentQualities.length - 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[originalIndex];
|
||||
Logger.printDebug(() -> "User clicked on quality: " + selectedQuality);
|
||||
|
||||
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);
|
||||
|
||||
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<String> {
|
||||
private int selectedPosition = -1;
|
||||
|
||||
public CustomQualityAdapter(@NonNull Context context, @NonNull List<String> objects) {
|
||||
super(context, 0, objects);
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.google.android.libraries.youtube.innertube.model.media;
|
||||
|
||||
public abstract class VideoQuality implements Comparable<VideoQuality> {
|
||||
public abstract String patch_getQualityName();
|
||||
|
||||
public abstract int patch_getResolution();
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
||||
org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
version = 5.31.3-dev.1
|
||||
version = 5.33.0-dev.13
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.10.1",
|
||||
"semantic-release": "^24.2.6"
|
||||
"semantic-release": "^24.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -6889,10 +6889,11 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semantic-release": {
|
||||
"version": "24.2.6",
|
||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.6.tgz",
|
||||
"integrity": "sha512-D0cwjlO5RZzHHxAcsoF1HxiRLfC3ehw+ay+zntzFs6PNX6aV0JzKNG15mpxPipBYa/l4fHly88dHvgDyqwb1Ww==",
|
||||
"version": "24.2.7",
|
||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
|
||||
"integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@semantic-release/commit-analyzer": "^13.0.0-beta.1",
|
||||
"@semantic-release/error": "^4.0.0",
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.10.1",
|
||||
"semantic-release": "^24.2.6"
|
||||
"semantic-release": "^24.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +424,10 @@ public final class app/revanced/patches/openinghours/misc/fix/crash/FixCrashPatc
|
||||
public static final fun getFixCrashPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/orfon/detection/root/RemoveRootDetectionPatchKt {
|
||||
public static final fun getRemoveRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/pandora/ads/DisableAudioAdsPatchKt {
|
||||
public static final fun getDisableAudioAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -476,6 +480,10 @@ public final class app/revanced/patches/primevideo/misc/permissions/RenamePermis
|
||||
public static final fun getRenamePermissionsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/primevideo/video/speed/PlaybackSpeedPatchKt {
|
||||
public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/protonmail/account/RemoveFreeAccountsLimitPatchKt {
|
||||
public static final fun getRemoveFreeAccountsLimitPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
@@ -1656,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;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ internal val createInboxSubTabsFingerprint = fingerprint {
|
||||
}
|
||||
|
||||
internal val loadInboxAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("V")
|
||||
strings(
|
||||
"ads_load_begin",
|
||||
|
||||
@@ -5,6 +5,5 @@ import app.revanced.patcher.fingerprint
|
||||
internal val isFacebookButtonEnabledFingerprint = fingerprint {
|
||||
parameters()
|
||||
returns("Z")
|
||||
strings("com.facebook.messaging.inbox.tab.plugins.core.tabtoolbarbutton." +
|
||||
"facebookbutton.facebooktoolbarbutton.FacebookButtonTabButtonImplementation")
|
||||
strings("FacebookButtonTabButtonImplementation")
|
||||
}
|
||||
|
||||
@@ -8,12 +8,7 @@ val hideVideoAdsPatch = bytecodePatch(
|
||||
name = "Hide music video ads",
|
||||
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
navigate(showVideoAdsParentFingerprint.originalMethod)
|
||||
|
||||
@@ -8,12 +8,7 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
|
||||
name = "Enable exclusive audio playback",
|
||||
description = "Enables the option to play audio without video.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
allowExclusiveAudioPlaybackFingerprint.method.returnEarly(true)
|
||||
|
||||
@@ -11,12 +11,7 @@ val permanentRepeatPatch = bytecodePatch(
|
||||
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex
|
||||
|
||||
@@ -7,18 +7,9 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
@Suppress("unused")
|
||||
val permanentShufflePatch = bytecodePatch(
|
||||
description = "Permanently remember your shuffle preference " +
|
||||
"even if the playlist ends or another track is played.",
|
||||
use = false,
|
||||
"even if the playlist ends or another track is played."
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
),
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
disableShuffleFingerprint.method.addInstruction(0, "return-void")
|
||||
|
||||
@@ -11,12 +11,7 @@ val hideCategoryBar = bytecodePatch(
|
||||
description = "Hides the category bar at the top of the homepage.",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
constructCategoryBarFingerprint.method.apply {
|
||||
|
||||
@@ -11,12 +11,7 @@ val hideGetPremiumPatch = bytecodePatch(
|
||||
name = "Hide 'Get Music Premium' label",
|
||||
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
hideGetPremiumFingerprint.method.apply {
|
||||
|
||||
@@ -18,12 +18,7 @@ val removeUpgradeButtonPatch = bytecodePatch(
|
||||
name = "Remove upgrade button",
|
||||
description = "Removes the upgrade tab from the pivot bar.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
pivotBarConstructorFingerprint.method.apply {
|
||||
|
||||
@@ -8,12 +8,7 @@ val bypassCertificateChecksPatch = bytecodePatch(
|
||||
name = "Bypass certificate checks",
|
||||
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
checkCertificateFingerprint.method.returnEarly(true)
|
||||
|
||||
@@ -8,12 +8,7 @@ val backgroundPlaybackPatch = bytecodePatch(
|
||||
name = "Remove background playback restrictions",
|
||||
description = "Removes restrictions on background playback, including playing kids videos in the background.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
execute {
|
||||
kidsBackgroundPlaybackPolicyControllerFingerprint.method.addInstruction(
|
||||
|
||||
@@ -25,12 +25,7 @@ val spoofClientPatch = bytecodePatch(
|
||||
name = "Spoof client",
|
||||
description = "Spoofs the client to fix playback.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.16.53",
|
||||
"8.05.51"
|
||||
)
|
||||
)
|
||||
compatibleWith("com.google.android.apps.youtube.music")
|
||||
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.revanced.patches.nfctoolsse.misc.pro
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
@Deprecated("This patch no longer works and will soon be deleted.")
|
||||
internal val isLicenseRegisteredFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
|
||||
@@ -4,9 +4,8 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockProPatch = bytecodePatch(
|
||||
name = "Unlock pro",
|
||||
) {
|
||||
@Deprecated("This patch no longer works and will soon be deleted.")
|
||||
val unlockProPatch = bytecodePatch{
|
||||
compatibleWith("com.wakdev.apps.nfctools.se")
|
||||
|
||||
execute {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.orfon.detection.root
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val isDeviceRootedFingeprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
custom { method, classDef ->
|
||||
method.name == "isDeviceRooted" &&
|
||||
classDef.endsWith("/RootChecker;")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package app.revanced.patches.orfon.detection.root
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val removeRootDetectionPatch = bytecodePatch(
|
||||
name = "Remove root detection",
|
||||
description = "Removes the check for root permissions.",
|
||||
) {
|
||||
compatibleWith("com.nousguide.android.orftvthek")
|
||||
|
||||
execute {
|
||||
isDeviceRootedFingeprint.method.returnEarly(false)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ val skipAdsPatch = bytecodePatch(
|
||||
name = "Skip ads",
|
||||
description = "Automatically skips video stream ads.",
|
||||
) {
|
||||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257"))
|
||||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.412.2947"))
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.primevideo.video.speed
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val playbackUserControlsInitializeFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
parameters("Lcom/amazon/avod/playbackclient/PlaybackInitializationContext;")
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "initialize" && classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val playbackUserControlsPrepareForPlaybackFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
parameters("Lcom/amazon/avod/playbackclient/PlaybackContext;")
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "prepareForPlayback" &&
|
||||
classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package app.revanced.patches.primevideo.video.speed
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.primevideo.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch;"
|
||||
|
||||
val playbackSpeedPatch = bytecodePatch(
|
||||
name = "Playback speed",
|
||||
description = "Adds playback speed controls to the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.amazon.avod.thirdpartyclient"("3.0.412.2947")
|
||||
)
|
||||
|
||||
execute {
|
||||
playbackUserControlsInitializeFingerprint.method.apply {
|
||||
val getIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.name == "mUserControls"
|
||||
}
|
||||
|
||||
val getRegister = getInstruction<OneRegisterInstruction>(getIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
getIndex + 1,
|
||||
"""
|
||||
invoke-static { v$getRegister }, $EXTENSION_CLASS_DESCRIPTOR->initializeSpeedOverlay(Landroid/view/View;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
playbackUserControlsPrepareForPlaybackFingerprint.method.apply {
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-virtual { p1 }, Lcom/amazon/avod/playbackclient/PlaybackContext;->getPlayer()Lcom/amazon/video/sdk/player/Player;
|
||||
move-result-object v0
|
||||
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setPlayer(Lcom/amazon/video/sdk/player/Player;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.InputType
|
||||
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
|
||||
@@ -40,7 +39,10 @@ private val downloadsResourcePatch = resourcePatch {
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_external_downloader"),
|
||||
SwitchPreference("revanced_external_downloader_action_button"),
|
||||
TextPreference("revanced_external_downloader_name", inputType = InputType.TEXT),
|
||||
TextPreference(
|
||||
"revanced_external_downloader_name",
|
||||
tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ val hideInfoCardsPatch = bytecodePatch(
|
||||
)
|
||||
|
||||
// Info cards can also appear as Litho components.
|
||||
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilterPatch;"
|
||||
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilter;"
|
||||
addLithoFilter(filterClassDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilterPatch;"
|
||||
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter;"
|
||||
|
||||
val returnYouTubeDislikePatch = bytecodePatch(
|
||||
name = "Return YouTube Dislike",
|
||||
|
||||
@@ -156,7 +156,7 @@ val lithoFilterPatch = bytecodePatch(
|
||||
move-object/from16 v$freeRegister, p2
|
||||
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
|
||||
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
|
||||
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->shouldFilter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :unfiltered
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ val recyclerViewTreeHookPatch = bytecodePatch {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
|
||||
recyclerViewTreeObserverFingerprint.method.apply {
|
||||
val insertIndex = recyclerViewTreeObserverFingerprint.patternMatch!!.startIndex + 1
|
||||
val recyclerViewParameter = 2
|
||||
|
||||
@@ -235,9 +235,9 @@ val settingsPatch = bytecodePatch(
|
||||
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
|
||||
}
|
||||
|
||||
// Add context override to force a specific settings language.
|
||||
licenseActivityOnCreateFingerprint.classDef.apply {
|
||||
val attachBaseContext = ImmutableMethod(
|
||||
// Add attachBaseContext method to override the context for setting a specific language.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"attachBaseContext",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)),
|
||||
@@ -255,13 +255,10 @@ val settingsPatch = bytecodePatch(
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}
|
||||
}.let(methods::add)
|
||||
|
||||
methods.add(attachBaseContext)
|
||||
}
|
||||
|
||||
licenseActivityOnCreateFingerprint.classDef.apply {
|
||||
val onBackPressed = ImmutableMethod(
|
||||
// Add onBackPressed method to handle back button presses, delegating to SearchViewController.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"onBackPressed",
|
||||
emptyList(),
|
||||
@@ -269,7 +266,7 @@ val settingsPatch = bytecodePatch(
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3)
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
@@ -281,10 +278,28 @@ val settingsPatch = bytecodePatch(
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
|
||||
};
|
||||
methods.add(onBackPressed);
|
||||
};
|
||||
// Add onConfigurationChanged method to handle configuration changes (e.g., screen orientation).
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"onConfigurationChanged",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/res/Configuration;", null, null)),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3)
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-super { p0, p1 }, Landroid/app/Activity;->onConfigurationChanged(Landroid/content/res/Configuration;)V
|
||||
invoke-static { p0, p1 }, $EXTENSION_CLASS_DESCRIPTOR->handleConfigurationChanged(Landroid/app/Activity;Landroid/content/res/Configuration;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
}
|
||||
|
||||
// Update shared dark mode status based on YT theme.
|
||||
// This is needed because YT allows forcing light/dark mode
|
||||
|
||||
@@ -121,7 +121,7 @@ internal val subtitleButtonControllerFingerprint = fingerprint {
|
||||
)
|
||||
}
|
||||
|
||||
internal val newVideoQualityChangedFingerprint = fingerprint {
|
||||
internal val videoQualityChangedFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
parameters("L")
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package app.revanced.patches.youtube.video.audio
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val streamingModelBuilderFingerprint = fingerprint {
|
||||
internal val formatStreamModelToStringFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
strings("vprng")
|
||||
returns("Ljava/lang/String;")
|
||||
custom { method, classDef ->
|
||||
method.name == "toString" && classDef.type ==
|
||||
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val menuItemAudioTrackFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
parameters("L")
|
||||
returns("V")
|
||||
strings("menu_item_audio_track")
|
||||
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
|
||||
|
||||
internal val selectAudioStreamFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("L")
|
||||
custom { method, _ ->
|
||||
method.parameters.size > 2 // Method has a large number of parameters and may change.
|
||||
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
|
||||
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
|
||||
}
|
||||
}
|
||||
|
||||
internal val audioStreamingTypeSelector = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
strings("raw") // String is not unique
|
||||
}
|
||||
@@ -5,22 +5,22 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
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.playservice.is_20_07_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.findMethodFromToString
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.insertLiteralOverride
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
@@ -37,6 +37,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
versionCheckPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
@@ -60,29 +61,25 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
)
|
||||
)
|
||||
|
||||
fun Method.firstFormatStreamingModelCall(
|
||||
returnType: String = "Ljava/lang/String;"
|
||||
): MutableMethod {
|
||||
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||
&& reference.returnType == returnType
|
||||
}
|
||||
|
||||
return navigate(this).to(audioTrackIdIndex).stop()
|
||||
// Disable feature flag that ignores the default track flag
|
||||
// and instead overrides to the user region language.
|
||||
if (is_20_07_or_greater) {
|
||||
selectAudioStreamFingerprint.method.insertLiteralOverride(
|
||||
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
|
||||
)
|
||||
}
|
||||
|
||||
// Accessor methods of FormatStreamModel have no string constants and
|
||||
// opcodes are identical to other methods in the same class,
|
||||
// so must walk from another class that use the methods.
|
||||
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
|
||||
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
|
||||
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
|
||||
val formatStreamModelClass = proxy(classes.first {
|
||||
it.type == audioTrackIdMethod.definingClass
|
||||
}).mutableClass
|
||||
val isDefaultAudioTrackMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("isDefaultAudioTrack=")
|
||||
val audioTrackDisplayNameMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("audioTrackDisplayName=")
|
||||
val audioTrackIdMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("audioTrackId=")
|
||||
|
||||
formatStreamModelClass.apply {
|
||||
proxy(classes.first {
|
||||
it.type == audioTrackIdMethod.definingClass
|
||||
}).mutableClass.apply {
|
||||
// Add a new field to store the override.
|
||||
val helperFieldName = "isDefaultAudioTrackOverride"
|
||||
fields.add(
|
||||
@@ -103,7 +100,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
|
||||
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
|
||||
val helperMethodClass = type
|
||||
val helperMethodName = "extension_isDefaultAudioTrack"
|
||||
val helperMethodName = "patch_isDefaultAudioTrack"
|
||||
val helperMethod = ImmutableMethod(
|
||||
helperMethodClass,
|
||||
helperMethodName,
|
||||
@@ -143,7 +140,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
methods.add(helperMethod)
|
||||
|
||||
// Modify isDefaultAudioTrack() to call extension helper method.
|
||||
isDefaultMethod.apply {
|
||||
isDefaultAudioTrackMethod.apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patches.youtube.video.information
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
@@ -110,7 +110,7 @@ internal val seekRelativeFingerprint = fingerprint {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves with the class found in [newVideoQualityChangedFingerprint].
|
||||
* Resolves with the class found in [videoQualityChangedFingerprint].
|
||||
*/
|
||||
internal val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
|
||||
@@ -9,7 +9,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.video.playerresponse.Hook
|
||||
import app.revanced.patches.youtube.video.playerresponse.addPlayerResponseMethodHook
|
||||
import app.revanced.patches.youtube.video.playerresponse.playerResponseMethodHookPatch
|
||||
@@ -263,7 +263,7 @@ val videoInformationPatch = bytecodePatch(
|
||||
|
||||
// Handle new playback speed menu.
|
||||
playbackSpeedMenuSpeedChangedFingerprint.match(
|
||||
newVideoQualityChangedFingerprint.originalClassDef,
|
||||
videoQualityChangedFingerprint.originalClassDef,
|
||||
).method.apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.IGET)
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,25 @@ import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal const val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE = "Lcom/google/android/libraries/youtube/innertube/model/media/VideoQuality;"
|
||||
|
||||
internal val videoQualityFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
parameters(
|
||||
"I", // Resolution.
|
||||
"Ljava/lang/String;", // Human readable resolution: "480p", "1080p Premium", etc
|
||||
"Z",
|
||||
"L"
|
||||
)
|
||||
custom { _, classDef ->
|
||||
classDef.type == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches with the class found in [videoQualitySetterFingerprint].
|
||||
*/
|
||||
internal val setQualityByIndexMethodClassFieldReferenceFingerprint = fingerprint {
|
||||
internal val setVideoQualityFingerprint = fingerprint {
|
||||
returns("V")
|
||||
parameters("L")
|
||||
opcodes(
|
||||
@@ -23,6 +38,22 @@ internal val videoQualityItemOnClickParentFingerprint = fingerprint {
|
||||
strings("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT")
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to class found in [videoQualityItemOnClickFingerprint].
|
||||
*/
|
||||
internal val videoQualityItemOnClickFingerprint = fingerprint {
|
||||
returns("V")
|
||||
parameters(
|
||||
"Landroid/widget/AdapterView;",
|
||||
"Landroid/view/View;",
|
||||
"I",
|
||||
"J"
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.name == "onItemClick"
|
||||
}
|
||||
}
|
||||
|
||||
internal val videoQualitySetterFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
@@ -37,7 +68,6 @@ internal val videoQualitySetterFingerprint = fingerprint {
|
||||
strings("menu_item_video_quality")
|
||||
}
|
||||
|
||||
|
||||
internal val videoQualityMenuOptionsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC)
|
||||
returns("[L")
|
||||
|
||||
@@ -3,8 +3,8 @@ package app.revanced.patches.youtube.video.quality
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
@@ -12,15 +12,21 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.video.information.onCreateHook
|
||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch;"
|
||||
private const val EXTENSION_VIDEO_QUALITY_MENU_INTERFACE =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch\$VideoQualityMenuInterface;"
|
||||
|
||||
val rememberVideoQualityPatch = bytecodePatch {
|
||||
dependsOn(
|
||||
@@ -61,81 +67,152 @@ 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 {
|
||||
// Fix bad data used by YouTube.
|
||||
it.method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p2, p1 }, $EXTENSION_CLASS_DESCRIPTOR->fixVideoQualityResolution(Ljava/lang/String;I)I
|
||||
move-result p1
|
||||
"""
|
||||
)
|
||||
|
||||
// Add methods to access obfuscated quality fields.
|
||||
it.classDef.apply {
|
||||
methods.add(
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"patch_getQualityName",
|
||||
listOf(),
|
||||
"Ljava/lang/String;",
|
||||
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
// Only one string field.
|
||||
val qualityNameField = fields.single { field ->
|
||||
field.type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
iget-object v0, p0, $qualityNameField
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
methods.add(
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"patch_getResolution",
|
||||
listOf(),
|
||||
"I",
|
||||
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
val resolutionField = fields.single { field ->
|
||||
field.type == "I"
|
||||
}
|
||||
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
iget v0, p0, $resolutionField
|
||||
return v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject a call to set the remembered quality once a video loads.
|
||||
setQualityByIndexMethodClassFieldReferenceFingerprint.match(
|
||||
videoQualitySetterFingerprint.originalClassDef,
|
||||
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 getOnItemClickListenerClassReference =
|
||||
val onItemClickListenerClassReference =
|
||||
(instructions.elementAt(0) as ReferenceInstruction).reference
|
||||
val getSetQualityByIndexMethodClassFieldReference =
|
||||
(instructions.elementAt(1) as ReferenceInstruction).reference
|
||||
val setQualityFieldReference =
|
||||
((instructions.elementAt(1) as ReferenceInstruction).reference) as FieldReference
|
||||
|
||||
val setQualityByIndexMethodClassFieldReference =
|
||||
getSetQualityByIndexMethodClassFieldReference as FieldReference
|
||||
proxy(
|
||||
classes.find { classDef ->
|
||||
classDef.type == setQualityFieldReference.type
|
||||
}!!
|
||||
).mutableClass.apply {
|
||||
// Add interface and helper methods to allow extension code to call obfuscated methods.
|
||||
interfaces.add(EXTENSION_VIDEO_QUALITY_MENU_INTERFACE)
|
||||
|
||||
val setQualityByIndexMethodClass = classes
|
||||
.find { classDef -> classDef.type == setQualityByIndexMethodClassFieldReference.type }!!
|
||||
methods.add(
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"patch_setQuality",
|
||||
listOf(
|
||||
ImmutableMethodParameter(YOUTUBE_VIDEO_QUALITY_CLASS_TYPE, null, null)
|
||||
),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
val setQualityMenuIndexMethod = methods.single { method ->
|
||||
method.parameterTypes.firstOrNull() == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE
|
||||
}
|
||||
|
||||
// Get the name of the setQualityByIndex method.
|
||||
val setQualityByIndexMethod = setQualityByIndexMethodClass.methods
|
||||
.find { method -> method.parameterTypes.first() == "I" }
|
||||
?: throw PatchException("Could not find setQualityByIndex method")
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-virtual { p0, p1 }, $setQualityMenuIndexMethod
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
videoQualitySetterFingerprint.method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
# Get the object instance to invoke the setQualityByIndex method on.
|
||||
iget-object v0, p0, $getOnItemClickListenerClassReference
|
||||
iget-object v0, v0, $getSetQualityByIndexMethodClassFieldReference
|
||||
# Get object instance to invoke setQuality method.
|
||||
iget-object v0, p0, $onItemClickListenerClassReference
|
||||
iget-object v0, v0, $setQualityFieldReference
|
||||
|
||||
# Get the method name.
|
||||
const-string v1, "${setQualityByIndexMethod.name}"
|
||||
|
||||
# Set the quality.
|
||||
# The first parameter is the array list of video qualities.
|
||||
# The second parameter is the index of the selected quality.
|
||||
# The register v0 stores the object instance to invoke the setQualityByIndex method on.
|
||||
# The register v1 stores the name of the setQualityByIndex method.
|
||||
invoke-static { p1, p2, v0, v1 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I
|
||||
invoke-static { p1, v0, p2 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([$YOUTUBE_VIDEO_QUALITY_CLASS_TYPE${EXTENSION_VIDEO_QUALITY_MENU_INTERFACE}I)I
|
||||
move-result p2
|
||||
""",
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// Inject a call to remember the selected quality.
|
||||
videoQualityItemOnClickParentFingerprint.classDef.methods.find { it.name == "onItemClick" }
|
||||
?.apply {
|
||||
val listItemIndexParameter = 3
|
||||
// Inject a call to remember the selected quality for Shorts.
|
||||
videoQualityItemOnClickFingerprint.match(
|
||||
videoQualityItemOnClickParentFingerprint.classDef
|
||||
).method.addInstruction(
|
||||
0,
|
||||
"invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V"
|
||||
)
|
||||
|
||||
// Inject a call to remember the user selected quality for regular videos.
|
||||
videoQualityChangedFingerprint.let {
|
||||
it.method.apply {
|
||||
val index = it.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
0,
|
||||
"invoke-static { p$listItemIndexParameter }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V",
|
||||
index + 1,
|
||||
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V",
|
||||
)
|
||||
} ?: throw PatchException("Failed to find onItemClick method")
|
||||
|
||||
// Remember video quality if not using old layout menu.
|
||||
newVideoQualityChangedFingerprint.method.apply {
|
||||
val index = newVideoQualityChangedFingerprint.patternMatch!!.startIndex
|
||||
val qualityRegister = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"invoke-static { v$qualityRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->userChangedQualityInNewFlyout(I)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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_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",
|
||||
"revanced_video_quality_dialog_button_unknown.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)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
package app.revanced.patches.youtube.video.speed.custom
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
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.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
@@ -18,18 +26,34 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTr
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstruction
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;"
|
||||
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter;"
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch;"
|
||||
|
||||
internal var speedUnavailableId = -1L
|
||||
private set
|
||||
|
||||
private val customPlaybackSpeedResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
speedUnavailableId = resourceMappings["string", "varispeed_unavailable_message"]
|
||||
}
|
||||
}
|
||||
|
||||
internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
description = "Adds custom playback speed options.",
|
||||
) {
|
||||
@@ -39,7 +63,8 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
addResourcesPatch,
|
||||
lithoFilterPatch,
|
||||
versionCheckPatch,
|
||||
recyclerViewTreeHookPatch
|
||||
recyclerViewTreeHookPatch,
|
||||
customPlaybackSpeedResourcePatch
|
||||
)
|
||||
|
||||
execute {
|
||||
@@ -48,6 +73,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
settingsMenuVideoSpeedGroup.addAll(
|
||||
listOf(
|
||||
SwitchPreference("revanced_custom_speed_menu"),
|
||||
SwitchPreference("revanced_restore_old_speed_menu"),
|
||||
TextPreference(
|
||||
"revanced_custom_playback_speeds",
|
||||
inputType = InputType.TEXT_MULTI_LINE
|
||||
@@ -77,15 +103,88 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
|
||||
}
|
||||
|
||||
|
||||
// Replace the speeds float array with custom speeds.
|
||||
// These speeds are used if the speed menu is immediately opened after a video is opened.
|
||||
speedArrayGeneratorFingerprint.method.apply {
|
||||
val sizeCallIndex = indexOfFirstInstructionOrThrow { getReference<MethodReference>()?.name == "size" }
|
||||
val sizeCallResultRegister = getInstruction<OneRegisterInstruction>(sizeCallIndex + 1).registerA
|
||||
|
||||
replaceInstruction(sizeCallIndex + 1, "const/4 v$sizeCallResultRegister, 0x0")
|
||||
|
||||
val arrayLengthConstIndex = indexOfFirstLiteralInstructionOrThrow(7)
|
||||
val arrayLengthConstDestination = getInstruction<OneRegisterInstruction>(arrayLengthConstIndex).registerA
|
||||
val playbackSpeedsArrayType = "$EXTENSION_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
|
||||
|
||||
addInstructions(
|
||||
arrayLengthConstIndex + 1,
|
||||
"""
|
||||
sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
|
||||
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
|
||||
""",
|
||||
)
|
||||
|
||||
val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
|
||||
}
|
||||
val originalArrayFetchDestination =
|
||||
getInstruction<OneRegisterInstruction>(originalArrayFetchIndex).registerA
|
||||
|
||||
replaceInstruction(
|
||||
originalArrayFetchIndex,
|
||||
"sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType",
|
||||
)
|
||||
}
|
||||
|
||||
// region Force old video quality menu.
|
||||
|
||||
// Add a static INSTANCE field to the class.
|
||||
// This is later used to call "showOldPlaybackSpeedMenu" on the instance.
|
||||
|
||||
val instanceField = ImmutableField(
|
||||
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||
"INSTANCE",
|
||||
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
).toMutable()
|
||||
|
||||
getOldPlaybackSpeedsFingerprint.classDef.staticFields.add(instanceField)
|
||||
// Set the INSTANCE field to the instance of the class.
|
||||
// In order to prevent a conflict with another patch, add the instruction at index 1.
|
||||
getOldPlaybackSpeedsFingerprint.method.addInstruction(1, "sput-object p0, $instanceField")
|
||||
|
||||
// Get the "showOldPlaybackSpeedMenu" method.
|
||||
// This is later called on the field INSTANCE.
|
||||
val showOldPlaybackSpeedMenuMethod = showOldPlaybackSpeedMenuFingerprint.match(
|
||||
getOldPlaybackSpeedsFingerprint.classDef,
|
||||
).method
|
||||
|
||||
// Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
|
||||
showOldPlaybackSpeedMenuExtensionFingerprint.method.apply {
|
||||
addInstructionsWithLabels(
|
||||
instructions.lastIndex,
|
||||
"""
|
||||
sget-object v0, $instanceField
|
||||
if-nez v0, :not_null
|
||||
return-void
|
||||
:not_null
|
||||
invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// Close the unpatched playback dialog and show the modern custom dialog.
|
||||
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
// Required to check if the playback speed menu is currently shown.
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region Custom tap and hold 2x speed.
|
||||
|
||||
if (is_19_25_or_greater) {
|
||||
|
||||
@@ -3,10 +3,33 @@ package app.revanced.patches.youtube.video.speed.custom
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
internal val getOldPlaybackSpeedsFingerprint = fingerprint {
|
||||
parameters("[L", "I")
|
||||
strings("menu_item_playback_speed")
|
||||
}
|
||||
|
||||
internal val showOldPlaybackSpeedMenuFingerprint = fingerprint {
|
||||
literal { speedUnavailableId }
|
||||
}
|
||||
|
||||
internal val showOldPlaybackSpeedMenuExtensionFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "showOldPlaybackSpeedMenu" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
internal val speedArrayGeneratorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("[L")
|
||||
parameters("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;")
|
||||
strings("0.0#")
|
||||
}
|
||||
|
||||
internal val speedLimiterFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
|
||||
@@ -32,7 +32,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstructio
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.EnumSet
|
||||
@@ -171,6 +174,79 @@ internal val Instruction.isBranchInstruction: Boolean
|
||||
internal val Instruction.isReturnInstruction: Boolean
|
||||
get() = this.opcode in returnOpcodes
|
||||
|
||||
/**
|
||||
* Find the instruction index used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
|
||||
val stringIndex = indexOfFirstInstruction {
|
||||
val reference = getReference<StringReference>()
|
||||
reference?.string?.contains(fieldName) == true
|
||||
}
|
||||
if (stringIndex < 0) {
|
||||
throw IllegalArgumentException("Could not find usage of string: '$fieldName'")
|
||||
}
|
||||
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
|
||||
|
||||
// Find use of the string with a StringBuilder.
|
||||
val stringUsageIndex = indexOfFirstInstruction(stringIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Ljava/lang/StringBuilder;" &&
|
||||
(this as? FiveRegisterInstruction)?.registerD == stringRegister
|
||||
}
|
||||
if (stringUsageIndex < 0) {
|
||||
throw IllegalArgumentException("Could not find StringBuilder usage in: $this")
|
||||
}
|
||||
|
||||
// Find the next usage of StringBuilder, which should be the desired field.
|
||||
val fieldUsageIndex = indexOfFirstInstruction(stringUsageIndex + 1) {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Ljava/lang/StringBuilder;" && reference.name == "append"
|
||||
}
|
||||
if (fieldUsageIndex < 0) {
|
||||
// Should never happen.
|
||||
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
|
||||
}
|
||||
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
|
||||
|
||||
// Look backwards up the method to find the instruction that sets the register.
|
||||
var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) {
|
||||
fieldUsageRegister == writeRegister
|
||||
}
|
||||
|
||||
// If the field is a method call, then adjust from MOVE_RESULT to the method call.
|
||||
val fieldSetOpcode = getInstruction(fieldSetIndex).opcode
|
||||
if (fieldSetOpcode == MOVE_RESULT ||
|
||||
fieldSetOpcode == MOVE_RESULT_WIDE ||
|
||||
fieldSetOpcode == MOVE_RESULT_OBJECT) {
|
||||
fieldSetIndex--
|
||||
}
|
||||
|
||||
return fieldSetIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the method used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
return navigate(this).to(methodUsageIndex).stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the field used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds public [AccessFlags] and removes private and protected flags (if present).
|
||||
*/
|
||||
@@ -594,7 +670,7 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
* starting from [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return The index of the instruction.
|
||||
@@ -617,7 +693,7 @@ fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = i
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
* starting from [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return The index of the instruction.
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">تم تعطيل تسجيلات تصحيح الأخطاء</string>
|
||||
<string name="revanced_debug_logs_none_found">لم يتم العثور على سجلات</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">تم نسخ السجلات</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">مسح سجلات تصحيح الأخطاء</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">يمسح جميع سجلات تصحيح أخطاء ReVanced المخزنة</string>
|
||||
<string name="revanced_debug_logs_clear_toast">تم مسح السجلات</string>
|
||||
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">يفتح زر التنزيل أداة التنزيل الخارجية</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">يفتح زر التنزيل أداة التنزيل الأصلية داخل التطبيق</string>
|
||||
<string name="revanced_external_downloader_name_title">اسم حزمة أداة التنزيل</string>
|
||||
<string name="revanced_external_downloader_name_summary">اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل NewPipe أو Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">اسم حزمة تطبيق التنزيل الخارجي المثبت لديك</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">أدخل اسم الحزمة</string>
|
||||
<string name="revanced_external_downloader_other_item">أخرى</string>
|
||||
<string name="revanced_external_downloader_not_found_title">التطبيق غير مثبت</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">لم يتم تثبيت %s . الرجاء تثبيته.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"تعذر العثور على التطبيق المثبت باسم الحزمة: %s
|
||||
|
||||
تأكد من أن اسم الحزمة صحيح وأن التطبيق مثبت"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">لا يمكن أن يكون اسم الحزمة فارغًا</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">تعطيل إيماءة التمرير الدقيقة</string>
|
||||
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_seekbar_title">إخفاء شريط تقدم مشغل الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">تم إخفاء شريط تقدم الفيديو</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">يتم عرض شريط تقدم الفيديو</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">إخفاء شريط تقدم صور مصغرة للفيديو</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">شريط تقدم صور مصغرة للفيديو مخفي</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">شريط تقدم صور مصغرة للفيديو معروض</string>
|
||||
@@ -1189,8 +1197,6 @@ Second \"item\" text"</string>
|
||||
سيؤدي هذا إلى تغيير مظهر ومميزات التطبيق، ولكن قد تحدث تأثيرات جانبية غير معروفة.
|
||||
|
||||
إذا تم إيقاف تشغيله لاحقًا، من المستحسن مسح بيانات التطبيق لمنع حدوث أخطاء في واجهة المستخدم."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">الهدف من تغيير إصدار التطبيق</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - استعادة أيقونات مشغل Shorts القديمة</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - استعادة أيقونات التنقل القديمة</string>
|
||||
@@ -1459,10 +1465,18 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">الزر معروض. انقر مع الاستمرار لإعادة ضبط سرعة التشغيل إلى الوضع الافتراضي</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">لا يتم عرض الزر</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">عرض زر جودة الفيديو</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">الزر معروض. انقر مع الاستمرار لإعادة تعيين الجودة إلى الافتراضي</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">الزر غير معروض</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">قائمة سرعة التشغيل المخصصة</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">يتم عرض قائمة سرعة التشغيل المخصصة</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">لا يتم عرض قائمة سرعة التشغيل المخصصة</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">استعادة قائمة سرعة التشغيل القديمة</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">قائمة السرعة القديمة معروضة</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">قائمة السرعة الحديثة معروضة</string>
|
||||
<string name="revanced_custom_playback_speeds_title">سرعة التشغيل المخصصة</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">إضافة أو تغيير سرعة التشغيل المخصصة</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">يجب أن تكون سرعات التشغيل المخصصة أقل من %s</string>
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -225,6 +224,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -137,7 +137,7 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_debug_logs_disabled">Sazlama qeydi qapalıdır</string>
|
||||
<string name="revanced_debug_logs_none_found">Qeydlər tapılmadı</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Qeydlər köçürüldü</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Sazlama qeydlərini təmizlə</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Saxlanılan bütün ReVanced sazlama qeydlərini təmizləyir</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Qeydlər silindi</string>
|
||||
@@ -201,6 +201,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">Daha çox göstər düyməsi axtarış nəticələrində gizlidir</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Daha çox göstər düyməsi axtarış nəticələrində görünür</string>
|
||||
<string name="revanced_hide_surveys_title">Sorğuları gizlət</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Sorğular gizlədilib</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Sorğular görünür</string>
|
||||
<string name="revanced_hide_ticket_shelf_title">Bilet bölməsin gizlət</string>
|
||||
<string name="revanced_hide_ticket_shelf_summary_on">Bilet bölməsi gizlidir</string>
|
||||
<string name="revanced_hide_ticket_shelf_summary_off">Bilet bölməsi görünür</string>
|
||||
@@ -248,6 +251,8 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Zamanlanmış reaksiyalar gizlədilir</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Zamanlanmış reaksiyalar göstərilir</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_title">\"AI ilə yaradılan video xülasəsini\" gizlət</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Sİ ilə yaradılan video xülasə bölməsi gizlədilib</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Sİ ilə yaradılan video xülasə bölməsi görünür</string>
|
||||
<string name="revanced_hide_ask_section_title">Soruş\'u Gizlət</string>
|
||||
<string name="revanced_hide_ask_section_summary_on">Soruş bölməsi gizlidir</string>
|
||||
<string name="revanced_hide_ask_section_summary_off">\"Soruş\" bölməsi göstərilir</string>
|
||||
@@ -275,6 +280,7 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_description_components_screen_title">Video təsviri</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Video təsviri elementlərini gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Filtr çubuğu</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Axınlar, əlaqəli videolar, axtarış nəticələri və baxış tarixçəsində filtr cərgəsin gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Axınlarda gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Axınlarda gizlidir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Axınlarda göstər</string>
|
||||
@@ -284,6 +290,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Axtarış nəticələrində gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Axtarış nəticələrində gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Axtarış nəticələrində göstərilir</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Baxış tarixçəsində gizlət</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Baxış tarixçəsində gizlədilib</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Baxış tarixçəsində görünür</string>
|
||||
<string name="revanced_channel_screen_title">Kanal səhifəsi</string>
|
||||
<string name="revanced_channel_screen_summary">Kanal səhifə elementlərini gizlət və ya göstər</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -306,7 +315,12 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Mağazaya baxın düyməsi görünür</string>
|
||||
<string name="revanced_comments_screen_title">Şərhlər</string>
|
||||
<string name="revanced_comments_screen_summary">Şərhlər bölməsi elementlərin gizlət və ya göstər</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Sİ söhbət xülasəsini gizlət</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Sİ söhbət xülasəsi gizlidir</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Sİ söhbət xülasəsi görünür</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">AI Ṣərhlər Xülasəsini Gizlət</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Sİ şərhlər xülasəsi gizlidir</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Sİ şərhlər xülasəsi görünür</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Kanal təlimatlarını gizlət</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Kanal təlimatları gizlidir</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Kanal təlimatları görünür</string>
|
||||
@@ -454,8 +468,15 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Yükləmə düyməsi, xarici yükləyicinizi açır</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Yükləmə düyməsi tətbiqə xas yükləyicini açır</string>
|
||||
<string name="revanced_external_downloader_name_title">Yükləyici paketi adı</string>
|
||||
<string name="revanced_external_downloader_name_summary">NewPipe və ya Seal kimi quraşdırılan xarici yükləmə tətbiqinizin paket adı</string>
|
||||
<string name="revanced_external_downloader_name_summary">Quraşdırılan xarici yükləyici tətbiqinizin paket adı</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Paket adını yerləşdir</string>
|
||||
<string name="revanced_external_downloader_other_item">Digər</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Tətbiq quraşdırılmayıb</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s quraşdırılmayıb. Lütfən, bunu quraşdır.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Paket adı ilə quraşdırılan tətbiq tapılmadı: %s
|
||||
|
||||
Paket adının düzgün olduğun yoxla və tətbiqi quraşdırın"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Paket adı boş ola bilməz</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Dəqiq axtarış jestini qapadın</string>
|
||||
@@ -715,11 +736,17 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Sürüşən say animasiyası açıqdır</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Video oynadıcı irəliləyiş cizgisin gizlət</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Video oynadıcı irəliləyiş cizgisi gizlidir</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Video oynadıcı irəliləyiş cizgisi göstərilir</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Video miniatür irəliləyiş cizgisin gizlət</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video miniatür irəliləyiş cizgisi gizlidir</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video miniatür irəliləyiş cizgisi görünür</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Shorts oynadıcı</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcı elementlərini gizlət və ya göstər</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Shorts-u Ev axınında gizlət</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Ev axını və əlaqəli videolarda gizlidir</string>
|
||||
@@ -1169,8 +1196,6 @@ Avtomobil tərtibatı
|
||||
Bu tətbiqin görünüşün və xüsusiyyətlərin dəyişdirəcək, lakin bilinməyən yan təsirlər ola bilər.
|
||||
|
||||
Sonradan qapadılarsa, UI səhvlərin önləmək üçün tətbiq məlumatların silmək tövsiyə olunur."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Saxta tətbiq versiyası hədəfi</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Köhnə Shorts oynadıcı işarələrin bərpa et</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Köhnə fəaliyyət simvolların bərpa et</string>
|
||||
@@ -1439,10 +1464,15 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Düymə göstərilir. Oynatma sürətin standart olaraq qaytarmaq üçün toxunub saxla</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Düymə göstərilmir</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Fərdi oynatma sürəti siyahısı</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Fərdi sürət siyahısı göstərilir</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Fərdi sürət siyahısı göstərilmir</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Köhnə oynatma sürəti menyusunu bərpa et</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Köhnə sürət menyusu göstərilir</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Müasir sürət menyusu göstərilir</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Fərdi oynatma sürəti</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Fərdi oynatma sürətlərini əlavə et və ya dəyiş</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Fərdi sürətlər %s dəyərindən az olmalıdır</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">Адладачнае лагаванне адключана</string>
|
||||
<string name="revanced_debug_logs_none_found">Лагі не знойдзены</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Лагі скапіяваны</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Не атрымалася экспартаваць лагі: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Не ўдалося экспартаваць журналы: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Ачысціць адладачныя лагі</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Ачышчае ўсе захаваныя адладачныя лагі ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Лагі ачышчаны</string>
|
||||
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Кнопка \"Спампаваць\" адкрывае ваш знешні загрузнік</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Кнопка \"Спампаваць\" адкрывае ўласную праграму загрузкі ў праграме</string>
|
||||
<string name="revanced_external_downloader_name_title">Назва пакета загрузніка</string>
|
||||
<string name="revanced_external_downloader_name_summary">Імя пакета ўсталяванай знешняй праграмы загрузкі, напрыклад NewPipe або Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Назва пакета вашага ўсталяванага знешняга спампоўшчыка праграмы</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Увядзіце назву пакета</string>
|
||||
<string name="revanced_external_downloader_other_item">Іншае</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Праграма не ўстаноўлена</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s не ўсталяваны. Калі ласка, усталюйце яго.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Не ўдалося знайсці ўстаноўленую праграму з назвай пакета: %s
|
||||
|
||||
Праверце, ці правільная назва пакета і ці ўстаноўлена праграма"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Назва пакета не можа быць пустой</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Адключыць жэст дакладнага пошуку</string>
|
||||
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_seekbar_title">Схаваць панэль прагрэсу відэапрайгравальніка</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Панэль пошуку відэаплэера схавана</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Адлюстроўваецца панэль пошуку відэаплэера</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Схаваць панэль прагрэсу з эскізамі відэа</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Панэль прагрэсу з эскізамі відэа схавана</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Панэль прагрэсу з эскізамі відэа паказана</string>
|
||||
@@ -1190,8 +1198,6 @@ Second \"item\" text"</string>
|
||||
Гэта зменіць знешні выгляд і функцыі прыкладання, але могуць узнікнуць невядомыя пабочныя эфекты.
|
||||
|
||||
Калі пазней будзе адключана, рэкамендуецца ачысціць даныя прыкладання, каб пазбегнуць памылак у інтэрфейсе."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Падробка мэтавай версіі праграмы</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 — Восстановить старые значки плеера Shorts</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Аднаўленне старых значкоў навігацыі</string>
|
||||
@@ -1460,10 +1466,18 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Кнопка паказана. Націсніце і ўтрымлівайце, каб скінуць хуткасць прайгравання да стандартнай</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Кнопка не паказваецца</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Паказаць кнопку якасці відэа</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Кнопка паказана. Націсніце і ўтрымлівайце, каб скінуць якасць да па змаўчанні</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Кнопка не паказваецца</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Меню пользовательской скорости воспроизведения</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Меню пользовательской скорости отображается</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Меню пользовательской скорости не отображается</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Аднавіць старое меню хуткасці прайгравання</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Паказана старое меню хуткасці</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Паказана сучаснае меню хуткасці</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Карыстальніцкія хуткасці прайгравання</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Добавьте или измените пользовательскую скорость воспроизведения</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Нестандартныя хуткасці павінны быць менш за %s</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">Отстраняването на грешки е деактивирано</string>
|
||||
<string name="revanced_debug_logs_none_found">Не са намерени логове</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Логовете са копирани</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Изчистване на логовете за отстраняване на грешки</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Изчиства всички съхранени логове за отстраняване на грешки на ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Логовете са изчистени</string>
|
||||
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Бутонът за изтегляне отваря избраното от Вас външно приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Бутонът за изтегляне отваря вграденото приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_name_title">Име на пакета на изтеглящото приложение</string>
|
||||
<string name="revanced_external_downloader_name_summary">Име на пакета на приложението за изтегляне, като NewPipe или Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Име на пакета на вашето инсталирано външно приложение за изтегляне</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Въведете името на пакета</string>
|
||||
<string name="revanced_external_downloader_other_item">Други</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Приложението не е инсталирано</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s не е инсталиран. Инсталирайте го.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Не може да бъде намерено инсталирано приложение с име на пакет: %s
|
||||
|
||||
Проверете дали името на пакета е правилно и приложението е инсталирано"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Името на пакета не може да бъде празно</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Деактивиране на жеста за точно търсене</string>
|
||||
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_seekbar_title">Скриване на лентата за търсене на видео плейър</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Лентата за време на плейъра е скрита</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Лентата за време на плейъра се показва</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Скриване на лентата за търсене на миниатюри на видеоклипове</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Лентата за търсене на миниатюри на видеоклипове е скрита</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Лентата за търсене на миниатюри на видеоклипове е показана</string>
|
||||
@@ -1189,8 +1197,6 @@ Second \"item\" text"</string>
|
||||
Това ще промени външния вид и функциите на приложението, но може да възникнат неизвестни странични ефекти.
|
||||
|
||||
Ако по-късно бъде изключено, препоръчително е да изчистите данните на приложението, за да предотвратите грешки в потребителския интерфейс."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Подлъгване за версията на</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Възстановете старите икони на Shorts в плейъра</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Възстановяване на стари икони за навигация</string>
|
||||
@@ -1459,10 +1465,18 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Бутонът е показан. Докоснете и задръжте, за да върнете скоростта на възпроизвеждане към стойността по подразбиране</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Бутонът не е показан</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Покажи бутона за качество на видеото</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Бутонът е показан. Докоснете и задръжте, за да възстановите качеството до подразбиране</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Бутонът не е показан</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Менюто за потребителска скорост</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Менюто за потребителска скорост се показва</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Менюто за потребителска скорост не се показва</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Възстановяване на старото меню за скорост на възпроизвеждане</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Показва се старото меню за скорост</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Показва се модерното меню за скорост</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Персонализирани скорости на възпроизвеждане</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Добавете или променете скоростa на възпроизвеждане</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Персонализираните скорости трябва да са по-малки от %s</string>
|
||||
|
||||
@@ -133,7 +133,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_debug_logs_disabled">ডিবাগ লগিং নিষ্ক্রিয় করা হয়েছে</string>
|
||||
<string name="revanced_debug_logs_none_found">কোনো লগ পাওয়া যায়নি</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">লগ অনুলিপি করা হয়েছে</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">লগ রপ্তানি করতে ব্যর্থ: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">লগ এক্সপোর্ট করা যায়নি: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">ডিবাগ লগগুলি সাফ করুন</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">সমস্ত সঞ্চিত ReVanced ডিবাগ লগ সাফ করে</string>
|
||||
<string name="revanced_debug_logs_clear_toast">লগ সাফ করা হয়েছে</string>
|
||||
@@ -464,8 +464,15 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_external_downloader_action_button_summary_on">ডাউনলোড বোতামটি আপনার বাহিরের ডাউনলোডার খুলবে</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">ডাউনলোড বোতামটি নেটিভ ইন-অ্যাপ ডাউনলোডার খুলবে</string>
|
||||
<string name="revanced_external_downloader_name_title">ডাউনলোডারের প্যাকেজ নাম</string>
|
||||
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাইরের ডাউনলোডার অ্যাপের প্যাকেজ নাম, যেমন NewPipe বা Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাহ্যিক ডাউনলোডার অ্যাপের প্যাকেজের নাম</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">প্যাকেজের নাম লিখুন</string>
|
||||
<string name="revanced_external_downloader_other_item">অন্যান্য</string>
|
||||
<string name="revanced_external_downloader_not_found_title">অ্যাপ ইনস্টল করা নেই</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s ইনস্টল করা নেই, ইনস্টল করুন।</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"প্যাকেজের নাম: %s সহ ইনস্টল করা অ্যাপটি খুঁজে পাওয়া যায়নি
|
||||
|
||||
প্যাকেজের নামটি সঠিক এবং অ্যাপটি ইনস্টল করা আছে কিনা তা যাচাই করুন"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">প্যাকেজের নাম খালি রাখা যাবে না</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">ভিডিওর নির্দিষ্ট অংশে যাওয়ার অঙ্গভঙ্গি নিষ্ক্রিয় করুন</string>
|
||||
@@ -728,6 +735,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
|
||||
<string name="revanced_hide_seekbar_title">ভিডিও প্লেয়ারের সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">ভিডিও প্লেয়ারে সিকবার লুকিয়ে রয়েছে</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">ভিডিও প্লেয়ারে সিকবার প্রদর্শিত হয়েছে</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">ভিডিও থাম্বনেইল সিকবার লুকান</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">ভিডিও থাম্বনেইল সিকবার লুকানো আছে</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">ভিডিও থাম্বনেইল সিকবার দেখানো হয়েছে</string>
|
||||
@@ -1185,8 +1193,6 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
|
||||
এটি অ্যাপ্লিকেশনটির চেহারা এবং বৈশিষ্ট্য পরিবর্তন করবে, তবে অজানা পার্শ্ব প্রতিক্রিয়া হতে পারে।
|
||||
|
||||
পরে যদি বন্ধ করা হয়, UI বাগ এড়াতে অ্যাপ্লিকেশন ডেটা পরিষ্কার করার পরামর্শ দেওয়া হয়।"</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">স্পুফ অ্যাপ সংস্করণ লক্ষ্য</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - পুরনো Shorts প্লেয়ার আইকন পুনরুদ্ধার করুন</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - পুরনো নেভিগেশন আইকন পুনরুদ্ধার করুন</string>
|
||||
@@ -1455,10 +1461,18 @@ DeArrow সম্পর্কে আরও জানতে এখানে ট
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">বোতামটি দেখানো হয়েছে। প্লেব্যাক স্পীড ডিফল্টে রিসেট করতে ট্যাপ করে ধরে রাখুন।</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">বোতাম প্রদর্শিত হয়নি</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">ভিডিও গুণমান বোতাম দেখান</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">বোতামটি দেখানো হয়েছে। গুণমান ডিফল্টে রিসেট করতে ট্যাপ করে ধরে রাখুন।</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">বোতামটি দেখানো হয়নি।</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">কাস্টম প্লেব্যাক গতি মেনু</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">কাস্টম স্পিড মেনু দেখানো হচ্ছে</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">কাস্টম স্পিড মেনু দেখানো হচ্ছে না</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">পুরানো প্লেব্যাক গতি মেনু পুনরুদ্ধার করুন</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">পুরানো গতির মেনু দেখানো হয়েছে</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">আধুনিক গতির মেনু দেখানো হয়েছে</string>
|
||||
<string name="revanced_custom_playback_speeds_title">নিজস্ব প্লেব্যাক স্পিড</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">কাস্টম প্লেব্যাক গতি যোগ করুন অথবা পরিবর্তন করুন</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">কাস্টম গতি %s এর চেয়ে কম হতে হবে</string>
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -137,7 +137,7 @@ Nebudete informováni o žádné neočekávané události."</string>
|
||||
<string name="revanced_debug_logs_disabled">Ladění je vypnuto</string>
|
||||
<string name="revanced_debug_logs_none_found">Nebyly nalezeny žádné protokoly</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Protokoly zkopírovány</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Vymazat ladicí protokoly</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Vymaže všechny uložené ladicí protokoly ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Protokoly vymazány</string>
|
||||
@@ -468,8 +468,15 @@ Tato funkce je dostupná pouze pro starší zařízení"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Tlačítko pro stahování otevře váš externí stahovač</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Tlačítko pro stahování otevře nativní stahovač v aplikaci</string>
|
||||
<string name="revanced_external_downloader_name_title">Název balíčku stahovače</string>
|
||||
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování, například NewPipe nebo Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Zadejte název balíčku</string>
|
||||
<string name="revanced_external_downloader_other_item">Jiné</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Aplikace není nainstalována</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s není nainstalováno. Prosím, nainstalujte.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Nepodařilo se najít nainstalovanou aplikaci s názvem balíčku: %s
|
||||
|
||||
Zkontrolujte, zda je název balíčku správný a aplikace je nainstalována"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Název balíčku nemůže být prázdný</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Zakázat gesto pro přesné hledání</string>
|
||||
@@ -732,6 +739,7 @@ Chcete-li zobrazit nabídku zvukové stopy, změňte možnost „Zfalšovat stre
|
||||
<string name="revanced_hide_seekbar_title">Skrýt posuvník přehrávače videa</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Posuvník ve video přehrávači je skryt</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Posuvník ve video přehrávači je zobrazen</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skrýt posuvník miniatur videí</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Posuvník miniatur videí je skryt</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Posuvník miniatur videí je zobrazen</string>
|
||||
@@ -1189,8 +1197,6 @@ Rozložení automobilu
|
||||
To změní vzhled a funkce aplikace, ale mohou se objevit neznámé vedlejší efekty.
|
||||
|
||||
Pokud bude později vypnuta, doporučujeme vymazat data aplikace, aby se zabránilo chybám uživatelského rozhraní."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Cíl napodobení verze aplikace</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Obnovuje staré ikony Shorts přehrávače</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 – Obnovit staré ikony navigace</string>
|
||||
@@ -1459,10 +1465,18 @@ Povolením této funkce lze odemknout vyšší kvality videa"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Tlačítko je zobrazeno. Klepnutím a podržením obnovíte výchozí rychlost přehrávání</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Tlačítko se nezobrazuje</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Zobrazit tlačítko kvality videa</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Tlačítko je zobrazeno. Klepnutím a podržením obnovíte kvalitu na výchozí</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Tlačítko není zobrazeno.</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Menu vlastní rychlosti přehrávání</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Menu vlastní rychlosti se zobrazuje</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Menu vlastní rychlosti se nezobrazuje</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Obnovit staré menu rychlosti přehrávání</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Zobrazeno staré menu rychlosti</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Zobrazeno moderní menu rychlosti</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Vlastní rychlosti přehrávání</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Přidat nebo změnit vlastní rychlosti přehrávání</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Vlastní rychlosti musí být menší než %s</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
|
||||
<string name="revanced_debug_logs_disabled">Fejlsøgningslogning er deaktiveret</string>
|
||||
<string name="revanced_debug_logs_none_found">Ingen logfiler fundet</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Logfiler kopieret</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Ryd fejlsøgningslogfiler</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Rydder alle gemte ReVanced-fejlsøgningslogfiler</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Logfiler ryddet</string>
|
||||
@@ -468,8 +468,15 @@ Denne funktion er kun tilgængelig for ældre enheder"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Download-knappen åbner din eksterne downloader</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Download-knappen åbner den indfødte in-app downloader</string>
|
||||
<string name="revanced_external_downloader_name_title">Downloader pakkenavn</string>
|
||||
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne downloader-app, såsom NewPipe eller Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne download-app</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Indtast pakkenavnet</string>
|
||||
<string name="revanced_external_downloader_other_item">Andet</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Appen er ikke installeret</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s er ikke installeret. Installér den.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Kunne ikke finde installeret app med pakkenavn: %s
|
||||
|
||||
Kontrollér, at pakkenavnet er korrekt, og at appen er installeret"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Pakkenavnet må ikke være tomt</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Deaktivér præcis søgemåde</string>
|
||||
@@ -732,6 +739,7 @@ For at vise lydspormenuen skal du ændre \"Spoof videostream\" til iOS TV"</stri
|
||||
<string name="revanced_hide_seekbar_title">Skjul videoafspillerens søgelinje</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Videoafspillerens søgelinje er skjult</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Videoafspillerens søgelinje vises</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Skjul video-miniaturebilledernes søgelinje</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video-miniaturebilledernes søgelinje er skjult</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video-miniaturebilledernes søgelinje vises</string>
|
||||
@@ -1191,8 +1199,6 @@ Automotive-layout
|
||||
Dette ændrer appens udseende og funktioner, men ukendte bivirkninger kan forekomme.
|
||||
|
||||
Hvis det senere slås fra, anbefales det at rydde app-dataene for at forhindre UI-fejl."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Spoof app version mål</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Gendan gamle Shorts player ikoner</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Gendan gamle navigationsikoner</string>
|
||||
@@ -1461,10 +1467,18 @@ Aktivering af dette kan låse op for højere videokvalitet"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Knappen vises. Tryk og hold for at nulstille afspilningshastigheden til standard.</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Knap vises ikke</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Vis videokvalitetsknap</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Knap vises. Tryk og hold nede for at nulstille kvaliteten til standard</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Knappen vises ikke</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Tilpasset afspilningshastighed menu</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Tilpasset hastighed menu er vist</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Brugerdefineret hastighedsmenu vises ikke</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Gendan gammel afspilningshastighedsmenu</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Gammel hastighedsmenu vises</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Moderne hastighedsmenu vises</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Tilpasset afspilningshastighed</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Tilføj eller ændr den brugerdefinerede afspilningshastighed</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Brugerdefinerede hastigheder skal være mindre end %s</string>
|
||||
|
||||
@@ -72,7 +72,9 @@ Um neue Sprachen zu übersetzen, besuchen Sie translate.revanced.app"</string>
|
||||
<string name="gms_core_toast_not_installed_message">MicroG GmsCore ist nicht installiert. Installieren Sie es.</string>
|
||||
<string name="gms_core_dialog_title">Aktion notwendig</string>
|
||||
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore hat keine Berechtigung, im Hintergrund zu laufen.
|
||||
Folgen Sie der Anleitung \"Meine App nicht beenden\" für Ihr Telefon und wenden Sie die Anweisungen auf Ihre MicroG-Installation an.
|
||||
|
||||
Folgen Sie der Anleitung \"Don't kill my app\" für Ihr Gerät und wenden Sie die Anweisungen auf Ihre MicroG-Installation an.
|
||||
|
||||
Dies ist erforderlich, damit die App funktioniert."</string>
|
||||
<string name="gms_core_dialog_open_website_text">Website öffnen</string>
|
||||
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"Die Batterieoptimierung von MicroG GmsCore muss deaktiviert werden, um Probleme zu vermeiden.
|
||||
@@ -85,7 +87,7 @@ Tippen Sie auf die Schaltfläche \"Fortfahren\" und erlauben Sie die Optimierung
|
||||
<patch id="misc.settings.settingsPatch">
|
||||
<string name="revanced_settings_screen_00_about_title">Über</string>
|
||||
<string name="revanced_settings_screen_01_ads_title">Werbung</string>
|
||||
<string name="revanced_settings_screen_02_alt_thumbnails_title">Alternative Miniaturbilder</string>
|
||||
<string name="revanced_settings_screen_02_alt_thumbnails_title">Alternative Thumbnails</string>
|
||||
<string name="revanced_settings_screen_03_feed_title">Feed</string>
|
||||
<string name="revanced_settings_screen_04_general_title">Allgemein</string>
|
||||
<string name="revanced_settings_screen_05_player_title">Spieler</string>
|
||||
@@ -133,7 +135,7 @@ Sie werden nicht über unerwartete Ereignisse informiert."</string>
|
||||
<string name="revanced_debug_logs_disabled">Debug-Protokollierung ist deaktiviert</string>
|
||||
<string name="revanced_debug_logs_none_found">Keine Protokolle gefunden</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Protokolle kopiert</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Fehler beim Exportieren von Protokollen: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Fehler beim Exportieren der Protokolle: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Debug-Protokolle löschen</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Löscht alle gespeicherten ReVanced-Debug-Protokolle</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Protokolle gelöscht</string>
|
||||
@@ -463,8 +465,15 @@ Diese Funktion ist nur für ältere Geräte verfügbar"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Download-Button öffnet den externen Downloader</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Download-Button öffnet den nativen In-App-Downloader</string>
|
||||
<string name="revanced_external_downloader_name_title">Paketname des Downloaders</string>
|
||||
<string name="revanced_external_downloader_name_summary">Paketname deiner installierten externen Downloader-App wie NewPipe oder Siegel</string>
|
||||
<string name="revanced_external_downloader_name_summary">Paketname Ihrer installierten externen Downloader-App</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Paketnamen eingeben</string>
|
||||
<string name="revanced_external_downloader_other_item">Andere</string>
|
||||
<string name="revanced_external_downloader_not_found_title">App nicht installiert</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s ist nicht installiert. Installier es.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Installierte App mit Paketnamen nicht gefunden: %s
|
||||
|
||||
Stellen Sie sicher, dass der Paketname korrekt ist und die App installiert ist"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Der Paketname darf nicht leer sein</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Genaue Suchgeste deaktivieren</string>
|
||||
@@ -725,6 +734,7 @@ Um das Audiotrack-Menü anzuzeigen, ändere \"Video-Streams fälschen\" zu iOS T
|
||||
<string name="revanced_hide_seekbar_title">Video-Player-Suchleiste ausblenden</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Video-Player-Suchleiste ist ausgeblendet</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Suchleiste für Video-Player wird angezeigt</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Suchleiste für Video-Thumbnails ausblenden</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Suchleiste für Video-Thumbnails ist ausgeblendet</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Suchleiste für Video-Thumbnails ist eingeblendet</string>
|
||||
@@ -1182,8 +1192,6 @@ Automotive-Layout
|
||||
Dadurch ändert sich das Erscheinungsbild und die Funktionen der App, es können jedoch unbekannte Nebeneffekte auftreten.
|
||||
|
||||
Wenn Sie die Funktion später deaktivieren, wird empfohlen, die App-Daten zu löschen, um UI-Fehler zu vermeiden."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Spoof-App-Versionsziel</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Alte Shorts Spielersymbole wiederherstellen</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Alte Navigations-Symbole wiederherstellen</string>
|
||||
@@ -1452,10 +1460,18 @@ Durch Aktivieren dieser Option können höhere Videoqualitäten freigeschaltet w
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Die Schaltfläche wird angezeigt. Tippen und halten, um die Wiedergabegeschwindigkeit auf die Standardeinstellung zurückzusetzen.</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Button wird nicht angezeigt</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Videoqualität-Schaltfläche anzeigen</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Schaltfläche wird angezeigt. Tippen und halten, um die Qualität auf Standard zurückzusetzen</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Schaltfläche wird nicht angezeigt</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Benutzerdefiniertes Wiedergabegeschwindigkeitsmenü</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Benutzerdefiniertes Geschwindigkeitsmenü wird angezeigt</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Benutzerdefiniertes Geschwindigkeitsmenü wird nicht angezeigt</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Altes Wiedergabegeschwindigkeitsmenü wiederherstellen</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Altes Geschwindigkeitsmenü wird angezeigt</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Modernes Geschwindigkeitsmenü wird angezeigt</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Benutzerdefinierte Wiedergabegeschwindigkeiten</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Hinzufügen oder Ändern der benutzerdefinierten Wiedergabegeschwindigkeit</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Benutzerdefinierte Geschwindigkeiten müssen kleiner als %s sein</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_debug_logs_disabled">Η καταγραφή εντοπισμού σφαλμάτων είναι απενεργοποιημένη</string>
|
||||
<string name="revanced_debug_logs_none_found">Δεν βρέθηκαν αρχεία καταγραφής</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Τα αρχεία καταγραφής αντιγράφηκαν</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Αποτυχία εξαγωγής αρχείων καταγραφής: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Αποτυχία εξαγωγής αρχείων καταγραφής: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Εκκαθάριση αρχείων καταγραφής εντοπισμού σφαλμάτων</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Εκκαθάριση όλων των αποθηκευμένων αρχειών καταγραφής εντοπισμού σφαλμάτων ReVanced</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Τα αρχεία καταγραφής εκκαθαρίστηκαν</string>
|
||||
@@ -203,7 +203,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_show_more_button_title">Κουμπί «Εμφάνιση περισσότερων»</string>
|
||||
<string name="revanced_hide_show_more_button_summary_on">Κρυμμένο\n\nΑφορά το κουμπί «Εμφάνιση περισσότερων» στα αποτελέσματα αναζήτησης</string>
|
||||
<string name="revanced_hide_show_more_button_summary_off">Εμφανίζεται\n\nΑφορά το κουμπί «Εμφάνιση περισσότερων» στα αποτελέσματα αναζήτησης</string>
|
||||
<string name="revanced_hide_surveys_title">Απόκρυψη ερευνών</string>
|
||||
<string name="revanced_hide_surveys_title">Έρευνες</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Κρυμμένες</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Εμφανίζονται</string>
|
||||
<string name="revanced_hide_ticket_shelf_title">Ενότητα εισιτηρίων</string>
|
||||
@@ -243,7 +243,7 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_quick_actions_title">Γρήγορες ενέργειες</string>
|
||||
<string name="revanced_hide_quick_actions_summary_on">Κρυμμένες</string>
|
||||
<string name="revanced_hide_quick_actions_summary_off">Εμφανίζονται</string>
|
||||
<string name="revanced_hide_related_videos_title">Σχετικά βίντεο στις γρήγορες ενέργειες</string>
|
||||
<string name="revanced_hide_related_videos_title">Περισσότερα βίντεο στις γρήγορες ενέργειες</string>
|
||||
<string name="revanced_hide_related_videos_summary_on">Κρυμμένα</string>
|
||||
<string name="revanced_hide_related_videos_summary_off">Εμφανίζονται</string>
|
||||
<string name="revanced_hide_subscribers_community_guidelines_title">Οδηγίες συνδρομητών</string>
|
||||
@@ -253,8 +253,8 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_on">Κρυμμένες</string>
|
||||
<string name="revanced_hide_timed_reactions_summary_off">Εμφανίζονται</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_title">Σύνοψη βίντεο που δημιουργήθηκε από AI</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Η ενότητα σύνοψης βίντεο που δημιουργήθηκε από AI είναι κρυμμένη</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Ενότητα σύνοψης βίντεο που δημιουργείται από ΤΝ: Εμφανίζεται</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_ask_section_title">Ενότητα «Ερώτηση»</string>
|
||||
<string name="revanced_hide_ask_section_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_ask_section_summary_off">Εμφανίζεται</string>
|
||||
@@ -283,18 +283,18 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_description_components_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων περιγραφής βίντεο</string>
|
||||
<string name="revanced_hide_filter_bar_screen_title">Γραμμή φίλτρων</string>
|
||||
<string name="revanced_hide_filter_bar_screen_summary">Απόκρυψη ή εμφάνιση της γραμμής φίλτρων στις ροές, στα σχετικά βίντεο, στα αποτελέσματα αναζήτησης και στο ιστορικό παρακολούθησης</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Γραμμή κατηγοριών στις ροές</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Γραμμή φίλτρων στις ροές</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Γραμμή κατηγοριών στα σχετικά βίντεο</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Γραμμή φίλτρων στα σχετικά βίντεο</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Γραμμή κατηγοριών στα αποτελέσματα αναζήτησης</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Γραμμή φίλτρων στα αποτελέσματα αναζήτησης</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Απόκρυψη στο ιστορικό παρακολούθησης</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Κρυμμένα</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Εμφανίζονται</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Γραμμή φίλτρων στο ιστορικό παρακολούθησης</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_channel_screen_title">Σελίδα καναλιού</string>
|
||||
<string name="revanced_channel_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων της σελίδας καναλιού</string>
|
||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||
@@ -317,12 +317,12 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_visit_store_button_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_comments_screen_title">Σχόλια</string>
|
||||
<string name="revanced_comments_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων στα σχόλια</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Απόκρυψη σύνοψης συνομιλίας AI</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Η σύνοψη συνομιλίας AI είναι κρυμμένη</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Η σύνοψη συνομιλίας AI εμφανίζεται</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_title">Σύνοψη συζήτησης που δημιουργήθηκε από AI</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_comments_ai_summary_title">Σύνοψη σχολίων που δημιουργήθηκε από AI</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Η σύνοψη σχολίων AI είναι κρυμμένη</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Η σύνοψη σχολίων AI εμφανίζεται</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_comments_ai_summary_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_title">Οδηγίες καναλιού</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_on">Κρυμμένες</string>
|
||||
<string name="revanced_hide_comments_channel_guidelines_summary_off">Εμφανίζονται</string>
|
||||
@@ -461,17 +461,24 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||
<string name="revanced_external_downloader_screen_title">Εξωτερικές λήψεις</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Ρυθμίσεις για χρήση εξωτερικού προγράμματος λήψης</string>
|
||||
<string name="revanced_external_downloader_screen_summary">Ρυθμίσεις για χρήση εξωτερικής εφαρμογής λήψης</string>
|
||||
<string name="revanced_external_downloader_title">Εμφάνιση κουμπιού εξωτερικής λήψης</string>
|
||||
<string name="revanced_external_downloader_summary_on">Το κουμπί λήψης εμφανίζεται στην οθόνη αναπαραγωγής</string>
|
||||
<string name="revanced_external_downloader_summary_off">Το κουμπί λήψης δεν εμφανίζεται στην οθόνη αναπαραγωγής</string>
|
||||
<string name="revanced_external_downloader_summary_on">Το κουμπί λήψης εμφανίζεται στην οθόνη αναπαραγωγής</string>
|
||||
<string name="revanced_external_downloader_summary_off">Το κουμπί λήψης δεν εμφανίζεται στην οθόνη αναπαραγωγής</string>
|
||||
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
|
||||
<string name="revanced_external_downloader_action_button_title">Μετατροπή κουμπιού ενέργειας «Λήψη»</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Το κουμπί «Λήψη» θα ανοίγει το εξωτερικό πρόγραμμα λήψης σας</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Το κουμπί «Λήψη» θα ανοίγει το ενσωματωμένο πρόγραμμα λήψης του YouTube</string>
|
||||
<string name="revanced_external_downloader_name_title">Όνομα πακέτου προγράμματος λήψης</string>
|
||||
<string name="revanced_external_downloader_name_summary">Το όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης όπως το NewPipe ή το Seal</string>
|
||||
<string name="revanced_external_downloader_action_button_title">Αλλαγή λειτουργίας κουμπιού ενέργειας «Λήψη»</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Το κουμπί «Λήψη» ανοίγει την εξωτερική εφαρμογή λήψης σας</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Το κουμπί «Λήψη» ανοίγει το ενσωματωμένο πρόγραμμα λήψης του YouTube</string>
|
||||
<string name="revanced_external_downloader_name_title">Όνομα πακέτου εφαρμογής λήψης</string>
|
||||
<string name="revanced_external_downloader_name_summary">Το όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Εισάγετε το όνομα πακέτου</string>
|
||||
<string name="revanced_external_downloader_other_item">Άλλο</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Η εφαρμογή δεν έχει εγκατασταθεί</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">Το %s δεν είναι εγκατεστημένο. Παρακαλούμε εγκαταστήστε το.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Δεν ήταν δυνατή η εύρεση εγκατεστημένης εφαρμογής με όνομα πακέτου: %s
|
||||
|
||||
Επιβεβαιώστε ότι το όνομα πακέτου είναι σωστό και ότι η εφαρμογή είναι εγκατεστημένη"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Το όνομα πακέτου δεν μπορεί να είναι κενό</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Απενεργοποίηση ακριβής αναζήτησης</string>
|
||||
@@ -731,16 +738,17 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_disable_rolling_number_animations_summary_off">Οι αριθμοί κινούνται αυξανόμενοι εκθετικά</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<string name="revanced_hide_seekbar_title">Απόκρυψη γραμμής αναζήτησης αναπαραγωγής βίντεο</string>
|
||||
<string name="revanced_hide_seekbar_title">Γραμμή προόδου στην οθόνη αναπαραγωγής</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Απόκρυψη γραμμής αναζήτησης μικρογραφιών βίντεο</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Η γραμμή αναζήτησης μικρογραφιών βίντεο είναι κρυμμένη</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Η γραμμή αναζήτησης μικρογραφιών βίντεο εμφανίζεται</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Γραμμή προόδου στις μικρογραφίες βίντεο</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Κρυμμένη</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Εμφανίζεται</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<string name="revanced_shorts_player_screen_title">Οθόνη αναπαραγωγής Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων αναπαραγωγής Shorts</string>
|
||||
<string name="revanced_shorts_player_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων στην οθόνη αναπαραγωγής Shorts</string>
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
<string name="revanced_hide_shorts_home_title">Shorts στην αρχική ροή και στα σχετικά βίντεο</string>
|
||||
<string name="revanced_hide_shorts_home_summary_on">Κρυμμένα</string>
|
||||
@@ -861,9 +869,9 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_end_screen_suggested_video_summary_off">Εμφανίζεται</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
|
||||
<string name="revanced_hide_related_videos_overlay_title">Πλαίσιο σχετικών βίντεο στην πλήρη οθόνη</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_on">Κρυμμένο</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_off">Εμφανίζεται</string>
|
||||
<string name="revanced_hide_related_videos_overlay_title">Σχετικά βίντεο στην πλήρη οθόνη</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_on">Κρυμμένα\n\nΑφορά τα σχετικά βίντεο της τελικής οθόνης στη λειτουργία πλήρους οθόνης</string>
|
||||
<string name="revanced_hide_related_videos_overlay_summary_off">Εμφανίζονται\n\nΑφορά τα σχετικά βίντεο της τελικής οθόνης στη λειτουργία πλήρους οθόνης</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.time.hideTimestampPatch">
|
||||
<string name="revanced_hide_timestamp_title">Χρονική πρόοδος βίντεο</string>
|
||||
@@ -1190,8 +1198,6 @@ Second \"item\" text"</string>
|
||||
Αυτό θα αλλάξει την εμφάνιση και τα χαρακτηριστικά της εφαρμογής, αλλά ενδέχεται να εμφανιστούν άγνωστες παρενέργειες.
|
||||
|
||||
Αν αργότερα απενεργοποιηθεί, συνιστάται η εκκαθάριση δεδομένων της εφαρμογής για την αποφυγή σφαλμάτων UI."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Έκδοση παραποίησης της εφαρμογής</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Επαναφορά των παλιών εικονιδίων της οθόνης αναπαραγωγής Shorts</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Επαναφορά παλιών εικονιδίων γραμμής πλοήγησης</string>
|
||||
@@ -1458,10 +1464,18 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Το κουμπί εμφανίζεται. Πατήστε παρατεταμένα για επαναφορά της ταχύτητας αναπαραγωγής στην προεπιλογή</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Το κουμπί δεν εμφανίζεται</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Εμφάνιση κουμπιού αλλαγής ποιότητας βίντεο</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Το κουμπί εμφανίζεται. Πατήστε παρατεταμένα για επαναφορά της ποιότητας στην προεπιλογή</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Το κουμπί δεν εμφανίζεται</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Μενού προσαρμοσμένης ταχύτητας αναπαραγωγής</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Το μενού προσαρμοσμένης ταχύτητας αναπαραγωγής εμφανίζεται</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Το μενού προσαρμοσμένης ταχύτητας αναπαραγωγής δεν εμφανίζεται</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Επαναφορά παλιού μενού ταχύτητας αναπαραγωγής</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Το παλιό μενού ταχύτητας εμφανίζεται</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Το σύγχρονο μενού ταχύτητας εμφανίζεται</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Προσαρμοσμένες ταχύτητες αναπαραγωγής</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Προσθέστε ή αλλάξτε τις προσαρμοσμένες ταχύτητες αναπαραγωγής</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Οι ταχύτητες πρέπει να είναι μικρότερες από %sx</string>
|
||||
|
||||
@@ -29,26 +29,26 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">No instalado por ReVanced Manager</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time">Parcheado hace más de 10 minutos</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_days">Parcheado hace %s días</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">La fecha de compilación del APK está dañada</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">La fecha de compilación del APK está corrupta</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsResourcePatch">
|
||||
<string name="revanced_settings_submenu_title">Ajustes</string>
|
||||
<string name="revanced_settings_confirm_user_dialog_title">¿Está seguro de que quiere continuar?</string>
|
||||
<string name="revanced_settings_confirm_user_dialog_title">¿Estás seguro de que quieres continuar?</string>
|
||||
<string name="revanced_settings_reset">Restablecer</string>
|
||||
<string name="revanced_settings_reset_color">Restablecer color</string>
|
||||
<string name="revanced_settings_color_invalid">Color no válido</string>
|
||||
<string name="revanced_settings_restart_title">Reinicio necesario</string>
|
||||
<string name="revanced_settings_restart_dialog_message">Reinicie la aplicación para que este cambio surta efecto.</string>
|
||||
<string name="revanced_settings_restart_dialog_message">Reinicia la aplicación para que este cambio surta efecto.</string>
|
||||
<string name="revanced_settings_restart">Reiniciar</string>
|
||||
<string name="revanced_settings_import">Importar</string>
|
||||
<string name="revanced_settings_import_copy">Copiar</string>
|
||||
<string name="revanced_settings_import_reset">Configuración ReVanced restablecida por defecto</string>
|
||||
<string name="revanced_settings_import_reset">Ajustes de ReVanced restablecidos a los valores predeterminados</string>
|
||||
<string name="revanced_settings_import_success">Configuración importada de %d</string>
|
||||
<string name="revanced_settings_import_failure_parse">Error de importación: %s</string>
|
||||
<string name="revanced_settings_search_hint">Ajustes de búsqueda</string>
|
||||
<string name="revanced_settings_import_failure_parse">Importación fallida: %s</string>
|
||||
<string name="revanced_settings_search_hint">Buscar ajustes</string>
|
||||
<string name="revanced_settings_search_no_results_title">No se encontraron resultados para «%s»</string>
|
||||
<string name="revanced_settings_search_no_results_summary">Prueba con otra palabra clave</string>
|
||||
<string name="revanced_settings_search_remove_message">¿Quitar del historial de búsqueda?</string>
|
||||
<string name="revanced_settings_search_remove_message">¿Eliminar del historial de búsqueda?</string>
|
||||
<string name="revanced_show_menu_icons_title">Mostrar iconos de configuración de ReVanced</string>
|
||||
<string name="revanced_show_menu_icons_summary_on">Se muestran los iconos de configuración</string>
|
||||
<string name="revanced_show_menu_icons_summary_off">No se muestran los iconos de configuración</string>
|
||||
@@ -62,25 +62,26 @@ Para traducir nuevos idiomas, visita translate.revanced.app"</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<string name="revanced_settings_about_links_body">Estás usando la versión de ReVanced Patches: <i>%s</i></string>
|
||||
<string name="revanced_settings_about_links_dev_header">Nota</string>
|
||||
<string name="revanced_settings_about_links_dev_body">Esta versión es una pre-versión y puedes experimentar problemas inesperados</string>
|
||||
<string name="revanced_settings_about_links_dev_body">Esta versión es un pre-lanzamiento y podrías experimentar problemas inesperados</string>
|
||||
<string name="revanced_settings_about_links_header">Enlaces oficiales</string>
|
||||
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
|
||||
and changes made here must also be made there. -->
|
||||
</patch>
|
||||
<patch id="misc.gms.gmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<string name="gms_core_toast_not_installed_message">MicroG GmsCore no está instalado. Instálala.</string>
|
||||
<string name="gms_core_toast_not_installed_message">MicroG GmsCore no está instalado. Instálalo.</string>
|
||||
<string name="gms_core_dialog_title">Acción necesaria</string>
|
||||
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore no tiene permiso para ejecutarse en segundo plano.
|
||||
|
||||
Sigue la guía \"No cierres mi aplicación\" para su teléfono, y aplica las instrucciones a tu instalación de MicroG
|
||||
Sigue la guía \"Don't kill my app\" para tu teléfono, y aplica las instrucciones a tu instalación de MicroG
|
||||
|
||||
Esto es requerido para el funcionamiento de la APP"</string>
|
||||
Esto es requerido para el funcionamiento de la aplicación."</string>
|
||||
<string name="gms_core_dialog_open_website_text">Abrir sitio web</string>
|
||||
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"La optimización de la batería de MicroG GmsCore debe estar deshabilitada para evitar problemas.
|
||||
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"Las optimizaciones de batería para MicroG GmsCore deben estar desactivadas para evitar problemas.
|
||||
|
||||
Desactivar las optimizaciones de batería para MicroG no afectará negativamente el gasto de la batería
|
||||
"</string>
|
||||
Deshabilitar las optimizaciones de batería para MicroG no afectará negativamente el uso de la batería.
|
||||
|
||||
Toca el botón continuar y permite los cambios de optimización."</string>
|
||||
<string name="gms_core_dialog_continue_text">Continuar</string>
|
||||
</patch>
|
||||
</app>
|
||||
@@ -92,26 +93,26 @@ Desactivar las optimizaciones de batería para MicroG no afectará negativamente
|
||||
<string name="revanced_settings_screen_03_feed_title">Fuente</string>
|
||||
<string name="revanced_settings_screen_04_general_title">General</string>
|
||||
<string name="revanced_settings_screen_05_player_title">Reproductor</string>
|
||||
<string name="revanced_settings_screen_07_seekbar_title">Barra</string>
|
||||
<string name="revanced_settings_screen_07_seekbar_title">Barra de progreso</string>
|
||||
<string name="revanced_settings_screen_08_swipe_controls_title">Controles de deslizamiento</string>
|
||||
<string name="revanced_settings_screen_11_misc_title">Miscelánea</string>
|
||||
<string name="revanced_settings_screen_11_misc_title">Otros</string>
|
||||
<string name="revanced_settings_screen_12_video_title">Video</string>
|
||||
<string name="revanced_restore_old_settings_menus_title">Restaurar los menús de configuración antiguos</string>
|
||||
<string name="revanced_restore_old_settings_menus_title">Restaurar menús de configuración antiguos</string>
|
||||
<string name="revanced_restore_old_settings_menus_summary_on">Se muestran los menús de configuración antiguos</string>
|
||||
<string name="revanced_restore_old_settings_menus_summary_off">No se muestran los menús de configuración antiguos</string>
|
||||
<string name="revanced_settings_search_history_title">Mostrar el historial de búsqueda de ajustes</string>
|
||||
<string name="revanced_settings_search_history_summary_on">Se muestra el historial de búsqueda de ajustes</string>
|
||||
<string name="revanced_settings_search_history_summary_on">El historial de búsqueda de los ajustes está visible</string>
|
||||
<string name="revanced_settings_search_history_summary_off">El historial de búsqueda de ajustes no se muestra</string>
|
||||
</patch>
|
||||
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
||||
<string name="revanced_shorts_disable_background_playback_title">Desactivar la reproducción en segundo plano de Shorts</string>
|
||||
<string name="revanced_shorts_disable_background_playback_summary_on">La reproducción de Shorts en segundo plano está desactivada</string>
|
||||
<string name="revanced_shorts_disable_background_playback_summary_off">La reproducción de Shorts en segundo plano está activada</string>
|
||||
<string name="revanced_shorts_disable_background_playback_summary_on">La reproducción en segundo plano de Shorts está deshabilitada</string>
|
||||
<string name="revanced_shorts_disable_background_playback_summary_off">La reproducción en segundo plano de Shorts está habilitada</string>
|
||||
</patch>
|
||||
<patch id="misc.debugging.enableDebuggingPatch">
|
||||
<string name="revanced_debug_screen_title">Depuración</string>
|
||||
<string name="revanced_debug_screen_summary">Activar o desactivar las opciones de depuración</string>
|
||||
<string name="revanced_debug_title">Depurar registro</string>
|
||||
<string name="revanced_debug_title">Registro de depuración</string>
|
||||
<string name="revanced_debug_summary_on">Los registros de depuración están habilitados</string>
|
||||
<string name="revanced_debug_summary_off">Los registros de depuración están desactivados</string>
|
||||
<string name="revanced_debug_protobuffer_title">Búfer de protocolo de registro</string>
|
||||
@@ -125,39 +126,41 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
|
||||
<string name="revanced_debug_stacktrace_title">Registrar stack traces</string>
|
||||
<string name="revanced_debug_stacktrace_summary_on">Los registros de depuración incluyen stack trace</string>
|
||||
<string name="revanced_debug_stacktrace_summary_off">Los registros de depuración no incluyen stack trace</string>
|
||||
<string name="revanced_debug_toast_on_error_title">Mostrar brindis en error ReVanced</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_on">Se muestra un toast si se produce un error</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_off">No se muestra un toast si se produce un error</string>
|
||||
<string name="revanced_debug_toast_on_error_user_dialog_message">"Desactivar las notificaciones de error oculta todas las notificaciones de error de ReVanced."</string>
|
||||
<string name="revanced_debug_toast_on_error_title">Mostrar mensaje de error en ReVanced</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_on">Se muestra un mensaje si se produce un error</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_off">No se muestra un mensaje si se produce un error</string>
|
||||
<string name="revanced_debug_toast_on_error_user_dialog_message">"Desactivar los avisos de error oculta todas las notificaciones de error de ReVanced.
|
||||
|
||||
No se le notificará de ningún evento inesperado."</string>
|
||||
<string name="revanced_debug_export_logs_to_clipboard_title">Exportar registros de depuración</string>
|
||||
<string name="revanced_debug_export_logs_to_clipboard_summary">Copia los registros de depuración de ReVanced al portapapeles</string>
|
||||
<string name="revanced_debug_logs_disabled">El registro de depuración está desactivado</string>
|
||||
<string name="revanced_debug_logs_none_found">No se encontraron registros</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Registros copiados</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Error al exportar los registros: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Error al exportar registros: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Borrar registros de depuración</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Borra todos los registros de depuración de ReVanced almacenados</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Registros borrados</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<string name="revanced_hide_album_cards_title">Ocultar álbumes</string>
|
||||
<string name="revanced_hide_album_cards_title">Ocultar tarjetas de álbum</string>
|
||||
<string name="revanced_hide_album_cards_summary_on">Las tarjetas de álbum están ocultas</string>
|
||||
<string name="revanced_hide_album_cards_summary_off">Se muestran las tarjetas de álbum</string>
|
||||
<string name="revanced_hide_artist_cards_title">Ocultar tarjetas de artistas</string>
|
||||
<string name="revanced_hide_artist_cards_summary_on">Las tarjetas de artistas están ocultas</string>
|
||||
<string name="revanced_hide_artist_cards_summary_off">Se muestran las tarjetas del artista</string>
|
||||
<string name="revanced_hide_artist_cards_title">Ocultar tarjetas de artista</string>
|
||||
<string name="revanced_hide_artist_cards_summary_on">Las tarjetas de artista están ocultas</string>
|
||||
<string name="revanced_hide_artist_cards_summary_off">Se muestran las tarjetas de artista</string>
|
||||
<string name="revanced_hide_chips_shelf_title">Ocultar \"Más como esto\"</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_on">\"Más como esto\" está oculto</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_off">\"Más como esto\" está habilitado</string>
|
||||
<string name="revanced_hide_community_posts_title">Ocultar mensajes comunitarios</string>
|
||||
<string name="revanced_hide_community_posts_summary_on">Los mensajes de la comunidad están ocultos</string>
|
||||
<string name="revanced_hide_community_posts_title">Ocultar publicaciones de comunidad</string>
|
||||
<string name="revanced_hide_community_posts_summary_on">Las publicaciones de la comunidad están ocultas</string>
|
||||
<string name="revanced_hide_community_posts_summary_off">Se muestran las publicaciones de la comunidad</string>
|
||||
<string name="revanced_hide_compact_banner_title">Ocultar banners compactos</string>
|
||||
<string name="revanced_hide_compact_banner_summary_on">Los banners compactos están ocultos</string>
|
||||
<string name="revanced_hide_compact_banner_summary_off">Se muestran los banners compactos</string>
|
||||
<string name="revanced_hide_crowdfunding_box_title">Ocultar caja de recaudación</string>
|
||||
<string name="revanced_hide_crowdfunding_box_title">Ocultar caja de Crowdfunding</string>
|
||||
<string name="revanced_hide_crowdfunding_box_summary_on">La caja de Crowdfunding está oculta</string>
|
||||
<string name="revanced_hide_crowdfunding_box_summary_off">Se muestra la caja de Crowdfunding</string>
|
||||
<string name="revanced_hide_crowdfunding_box_summary_off">La caja de Crowdfunding está visible</string>
|
||||
<string name="revanced_hide_expandable_card_title">Ocultar tarjeta expandible</string>
|
||||
<string name="revanced_hide_expandable_card_summary_on">Tarjeta expandible debajo de los videos oculta</string>
|
||||
<string name="revanced_hide_expandable_card_summary_off">Tarjeta expandible debajo de los videos visible</string>
|
||||
@@ -172,19 +175,19 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
|
||||
• Más relevantes
|
||||
• Compras
|
||||
• Ver de nuevo"</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_off">Se muestran los estantes horizontales</string>
|
||||
<string name="revanced_hide_horizontal_shelves_summary_off">Los estantes horizontales están visibles</string>
|
||||
<string name="revanced_hide_image_shelf_title">Ocultar estantería de imágenes</string>
|
||||
<string name="revanced_hide_image_shelf_summary_on">Estantería de imágenes en los resultados de búsqueda oculta</string>
|
||||
<string name="revanced_hide_image_shelf_summary_off">Estantería de imágenes en los resultados de búsqueda visible</string>
|
||||
<string name="revanced_hide_latest_posts_title">Ocultar últimos mensajes</string>
|
||||
<string name="revanced_hide_latest_posts_title">Ocultar últimas publicaciones</string>
|
||||
<string name="revanced_hide_latest_posts_summary_on">Las últimas publicaciones están ocultas</string>
|
||||
<string name="revanced_hide_latest_posts_summary_off">Se muestran las últimas publicaciones</string>
|
||||
<string name="revanced_hide_mix_playlists_title">Ocultar listas de mezcla</string>
|
||||
<string name="revanced_hide_mix_playlists_summary_on">Las listas de reproducción mixtas están ocultas</string>
|
||||
<string name="revanced_hide_mix_playlists_summary_off">Mezclar listas de reproducción son mostradas</string>
|
||||
<string name="revanced_hide_mix_playlists_title">Ocultar lista de reproducción Mix</string>
|
||||
<string name="revanced_hide_mix_playlists_summary_on">La lista de reproducción Mix está oculta</string>
|
||||
<string name="revanced_hide_mix_playlists_summary_off">La lista de reproducción Mix está visible</string>
|
||||
<string name="revanced_hide_movies_section_title">Ocultar sección de películas</string>
|
||||
<string name="revanced_hide_movies_section_summary_on">La sección de películas está oculta</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">Se muestra la sección de películas</string>
|
||||
<string name="revanced_hide_movies_section_summary_off">La sección de películas está visible</string>
|
||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<string name="revanced_hide_notify_me_button_title">Ocultar el botón \'Notificarme\'</string>
|
||||
@@ -200,9 +203,9 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
|
||||
<string name="revanced_hide_show_more_button_summary_off">El botón Mostrar más en los resultados de búsqueda está visible</string>
|
||||
<string name="revanced_hide_surveys_title">Ocultar encuestas</string>
|
||||
<string name="revanced_hide_surveys_summary_on">Las encuestas están ocultas</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Las encuestas están mostradas</string>
|
||||
<string name="revanced_hide_ticket_shelf_title">Ocultar estante de boletos</string>
|
||||
<string name="revanced_hide_ticket_shelf_summary_on">El estante de boletos está oculto</string>
|
||||
<string name="revanced_hide_surveys_summary_off">Las encuestas están visibles</string>
|
||||
<string name="revanced_hide_ticket_shelf_title">Ocultar estantes de tickets</string>
|
||||
<string name="revanced_hide_ticket_shelf_summary_on">El estante de tickets está oculto</string>
|
||||
<string name="revanced_hide_ticket_shelf_summary_off">El estante de boletos está visible</string>
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
<string name="revanced_hide_video_recommendation_labels_title">Ocultar etiquetas de recomendación de vídeo</string>
|
||||
@@ -465,8 +468,15 @@ Esta función solo está disponible para dispositivos antiguos"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">El botón de descarga abre su descarga externa</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">El botón de descarga abre el descargador nativo en la aplicación</string>
|
||||
<string name="revanced_external_downloader_name_title">Nombre del paquete del descargado</string>
|
||||
<string name="revanced_external_downloader_name_summary">Nombre del paquete de su aplicación de descarga externa instalada, como NewPipe o Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Nombre del paquete de tu aplicación de descarga externa instalada</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Introduce el nombre del paquete</string>
|
||||
<string name="revanced_external_downloader_other_item">Otro</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Aplicación no instalada</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s no está instalado. Por favor, instálelo.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"No se pudo encontrar la aplicación instalada con el nombre de paquete: %s
|
||||
|
||||
Verifica que el nombre del paquete sea correcto y que la aplicación esté instalada"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">El nombre del paquete no puede estar vacío</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Desactivar gesto de búsqueda preciso</string>
|
||||
@@ -729,6 +739,7 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
|
||||
<string name="revanced_hide_seekbar_title">Ocultar barra de búsqueda del reproductor de vídeo</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">La barra de búsqueda del reproductor de vídeo está oculta</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">La barra de búsqueda del reproductor de vídeo se muestra</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Ocultar barra de búsqueda de miniaturas de vídeo</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">La barra de búsqueda de miniaturas de vídeo está oculta</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">La barra de búsqueda de miniaturas de vídeo está visible</string>
|
||||
@@ -1177,8 +1188,6 @@ Diseño para automóviles
|
||||
Esto cambiará la apariencia y las características de la aplicación, pero pueden producirse efectos secundarios desconocidos.
|
||||
|
||||
Si se desactiva posteriormente, se recomienda borrar los datos de la aplicación para evitar errores en la interfaz de usuario."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Versión de aplicación falsa de destino</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Restaurar iconos antiguos del reproductor de Shorts</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Restaurar iconos de navegación antiguos</string>
|
||||
@@ -1447,10 +1456,18 @@ Habilitar esto puede desbloquear calidades de vídeo más altas"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Se muestra el botón. Mantén pulsado para restablecer la velocidad de reproducción predeterminada</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">El botón no se muestra</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Mostrar botón de calidad de video</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Botón visible. Toca y mantén para restablecer la calidad a los valores predeterminados</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Botón no visible</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Menú de velocidad de reproducción personalizada</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Menú de velocidad personalizado se muestra</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Menú de velocidad personalizado no se muestra</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Restaurar el menú de velocidad de reproducción antiguo</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Se muestra el menú de velocidad antiguo</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Se muestra el menú de velocidad moderno</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Velocidades de reproducción personalizadas</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Añadir o cambiar las velocidades de reproducción personalizadas</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Las velocidades personalizadas deben ser menores que %s</string>
|
||||
|
||||
@@ -137,7 +137,7 @@ Teid ei teavitata ühestki ootamatust sündmusest."</string>
|
||||
<string name="revanced_debug_logs_disabled">Silumislogimine on keelatud</string>
|
||||
<string name="revanced_debug_logs_none_found">Logisid ei leitud</string>
|
||||
<string name="revanced_debug_logs_copied_to_clipboard">Logid kopeeritud</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Logide eksportimine ebaõnnestus: $s</string>
|
||||
<string name="revanced_debug_logs_failed_to_export">Logide eksportimine ebaõnnestus: %s</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_title">Puhasta silumislogid</string>
|
||||
<string name="revanced_debug_logs_clear_buffer_summary">Puhastab kõik salvestatud ReVanced silumislogid</string>
|
||||
<string name="revanced_debug_logs_clear_toast">Logid puhastatud</string>
|
||||
@@ -468,8 +468,15 @@ See funktsioon on saadaval ainult vanemates seadmetes"</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_on">Allalaadimise nupp avab teie välise allalaadija</string>
|
||||
<string name="revanced_external_downloader_action_button_summary_off">Allalaadimise nupp avab seadme sisseehitatud allalaadija</string>
|
||||
<string name="revanced_external_downloader_name_title">Allalaadija paketi nimi</string>
|
||||
<string name="revanced_external_downloader_name_summary">Teie installitud välise allalaadija rakenduse paketi nimi, näiteks NewPipe või Seal</string>
|
||||
<string name="revanced_external_downloader_name_summary">Paigaldatud välise allalaadimisrakenduse paketi nimi</string>
|
||||
<string name="revanced_external_downloader_other_item_hint">Sisesta paketi nimi</string>
|
||||
<string name="revanced_external_downloader_other_item">Muu</string>
|
||||
<string name="revanced_external_downloader_not_found_title">Rakendus pole installitud</string>
|
||||
<string name="revanced_external_downloader_not_installed_warning">%s ei ole installitud. Palun installige see.</string>
|
||||
<string name="revanced_external_downloader_package_not_found_warning">"Installitud rakendust ei leitud paketi nimega: %s
|
||||
|
||||
Veendu, et paketi nimi on õige ja rakendus on installitud"</string>
|
||||
<string name="revanced_external_downloader_empty_warning">Paketi nimi ei tohi olla tühi</string>
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
|
||||
<string name="revanced_disable_precise_seeking_gesture_title">Keela täpne otsingu žest</string>
|
||||
@@ -732,6 +739,7 @@ Heliriba menüü kuvamiseks muutke valikut „Võltsitud videovoogedastus“ vä
|
||||
<string name="revanced_hide_seekbar_title">Peida videopleieri edenemisriba</string>
|
||||
<string name="revanced_hide_seekbar_summary_on">Videopleieri otsimisriba on peidetud</string>
|
||||
<string name="revanced_hide_seekbar_summary_off">Videopleieri otsimisriba on nähtav</string>
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
<string name="revanced_hide_seekbar_thumbnail_title">Peida video pisipiltide edenemisriba</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video pisipiltide edenemisriba on peidetud</string>
|
||||
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video pisipiltide edenemisriba on nähtav</string>
|
||||
@@ -1189,8 +1197,6 @@ Autode paigutus
|
||||
See muudab rakenduse välimust ja funktsioone, kuid võivad esineda tundmatud kõrvalmõjud.
|
||||
|
||||
Kui see hiljem välja lülitatakse, on soovitatav rakenduse andmed kustutada, et vältida kasutajaliidese vigu."</string>
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
<string name="revanced_spoof_app_version_target_title">Võltsitud rakenduse versiooni siht</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Taastage vanad Shortsi esitajaikoonid</string>
|
||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 – Taasta vanad navigeerimisikoonid</string>
|
||||
@@ -1459,10 +1465,18 @@ Selle lubamine võib avada kõrgema video kvaliteedi"</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Nupp on nähtaval. Puudutage ja hoidke all, et taastada taasesituse kiirus vaikeväärtusele</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Nuppi ei kuvata</string>
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
<string name="revanced_video_quality_dialog_button_title">Näita video kvaliteedi nuppu</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_on">Nupp on nähtaval. Kvaliteedi lähtestamiseks vaikeseadeteks puudutage ja hoidke</string>
|
||||
<string name="revanced_video_quality_dialog_button_summary_off">Nuppu ei kuvata</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
<string name="revanced_custom_speed_menu_title">Kohandatud taasesituse kiiruse menüü</string>
|
||||
<string name="revanced_custom_speed_menu_summary_on">Kohandatud kiiruse menüü kuvatakse</string>
|
||||
<string name="revanced_custom_speed_menu_summary_off">Kohandatud kiiruse menüüd ei kuvata</string>
|
||||
<string name="revanced_restore_old_speed_menu_title">Taasta vana taasesituse kiiruse menüü</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_on">Kuvatakse vana kiiruse menüü</string>
|
||||
<string name="revanced_restore_old_speed_menu_summary_off">Kuvatakse kaasaegne kiiruse menüü</string>
|
||||
<string name="revanced_custom_playback_speeds_title">Kohandatud taasesituse kiirused</string>
|
||||
<string name="revanced_custom_playback_speeds_summary">Lisa või muuda kohandatud taasesituse kiirusi</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Kohandatud kiirused peavad olema alla %s</string>
|
||||
|
||||
@@ -127,6 +127,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
@@ -174,6 +174,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
|
||||
</patch>
|
||||
<patch id="layout.hide.seekbar.hideSeekbarPatch">
|
||||
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
|
||||
</patch>
|
||||
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
|
||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||
@@ -219,8 +220,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -273,6 +272,8 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.quality.button.videoQualityButtonPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
</patch>
|
||||
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user