From dfb5407e67222e80e23c8935e04b6dbf1a43d757 Mon Sep 17 00:00:00 2001 From: MarcaD <152095496+MarcaDian@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:54:09 +0300 Subject: [PATCH] feat(YouTube - Loop video): Add player button to change loop video state (#5961) --- .../youtube/patches/AutoRepeatPatch.java | 11 --- .../youtube/patches/LoopVideoPatch.java | 13 +++ .../extension/youtube/settings/Settings.java | 27 +++--- .../youtube/videoplayer/LoopVideoButton.java | 83 +++++++++++++++++++ patches/api/patches.api | 4 + .../player/fullscreen/ExitFullscreenPatch.kt | 6 +- .../misc/autorepeat/AutoRepeatPatch.kt | 61 +------------- .../youtube/misc/loopvideo/LoopVideoPatch.kt | 62 ++++++++++++++ .../loopvideo/button/LoopVideoButtonPatch.kt | 57 +++++++++++++ .../patches/youtube/shared/Fingerprints.kt | 7 +- .../resources/addresources/values/strings.xml | 15 +++- .../revanced_loop_video_button_off.xml | 11 +++ .../revanced_loop_video_button_on.xml | 9 ++ .../youtube_controls_bottom_ui_container.xml | 22 +++++ 14 files changed, 299 insertions(+), 89 deletions(-) delete mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AutoRepeatPatch.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LoopVideoPatch.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/LoopVideoButton.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/button/LoopVideoButtonPatch.kt create mode 100644 patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_off.xml create mode 100644 patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_on.xml create mode 100644 patches/src/main/resources/loopvideobutton/host/layout/youtube_controls_bottom_ui_container.xml diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AutoRepeatPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AutoRepeatPatch.java deleted file mode 100644 index 21409e739..000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AutoRepeatPatch.java +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.extension.youtube.patches; - -import app.revanced.extension.youtube.settings.Settings; - -@SuppressWarnings("unused") -public class AutoRepeatPatch { - //Used by app.revanced.patches.youtube.layout.autorepeat.patch.AutoRepeatPatch - public static boolean shouldAutoRepeat() { - return Settings.AUTO_REPEAT.get(); - } -} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LoopVideoPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LoopVideoPatch.java new file mode 100644 index 000000000..8504aad93 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/LoopVideoPatch.java @@ -0,0 +1,13 @@ +package app.revanced.extension.youtube.patches; + +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class LoopVideoPatch { + /** + * Injection point + */ + public static boolean shouldLoopVideo() { + return Settings.LOOP_VIDEO.get(); + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 7980bb5fa..63a8e9c8f 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -341,7 +341,8 @@ public class Settings extends BaseSettings { // Miscellaneous public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE); public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false); - public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE); + public static final BooleanSetting LOOP_VIDEO = new BooleanSetting("revanced_loop_video", FALSE); + public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE); @@ -444,28 +445,30 @@ public class Settings extends BaseSettings { public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false); // Deprecated migrations + private static final BooleanSetting DEPRECATED_AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE); private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true); private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE); private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127); - private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); + private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE); private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); private static final BooleanSetting DEPRECATED_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE); - public static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f, false, false); - public static final FloatSetting DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f, false, false); + private static final FloatSetting DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f, false, false); static { // region Migration + migrateOldSettingToNew(DEPRECATED_AUTO_REPEAT, LOOP_VIDEO); migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS); migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER); migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/LoopVideoButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/LoopVideoButton.java new file mode 100644 index 000000000..068ede3d2 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/LoopVideoButton.java @@ -0,0 +1,83 @@ +package app.revanced.extension.youtube.videoplayer; + +import static app.revanced.extension.shared.StringRef.str; + +import android.view.View; +import androidx.annotation.Nullable; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class LoopVideoButton { + @Nullable + private static PlayerControlButton instance; + + private static final int LOOP_VIDEO_ON = Utils.getResourceIdentifierOrThrow( + "revanced_loop_video_button_on", "drawable"); + private static final int LOOP_VIDEO_OFF = Utils.getResourceIdentifierOrThrow( + "revanced_loop_video_button_off", "drawable"); + + /** + * Injection point. + */ + public static void initializeButton(View controlsView) { + try { + instance = new PlayerControlButton( + controlsView, + "revanced_loop_video_button", + null, + Settings.LOOP_VIDEO_BUTTON::get, + v -> updateButtonAppearance(), + null + ); + } catch (Exception ex) { + Logger.printException(() -> "initializeButton failure", ex); + } + } + + /** + * injection point. + */ + public static void setVisibilityNegatedImmediate() { + if (instance != null) instance.setVisibilityNegatedImmediate(); + } + + /** + * 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); + } + + /** + * Updates the button's appearance. + */ + private static void updateButtonAppearance() { + if (instance == null) return; + + try { + Utils.verifyOnMainThread(); + + final boolean currentState = Settings.LOOP_VIDEO.get(); + final boolean newState = !currentState; + Settings.LOOP_VIDEO.save(newState); + + instance.setIcon(newState + ? LOOP_VIDEO_ON + : LOOP_VIDEO_OFF); + Utils.showToastShort(str(newState + ? "revanced_loop_video_button_toast_on" + : "revanced_loop_video_button_toast_off")); + } catch (Exception ex) { + Logger.printException(() -> "updateButtonAppearance failure", ex); + } + } +} diff --git a/patches/api/patches.api b/patches/api/patches.api index 76a7225f6..a54506b97 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1610,6 +1610,10 @@ public final class app/revanced/patches/youtube/misc/litho/filter/LithoFilterPat public static final fun getLithoFilterPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatchKt { + public static final fun getLoopVideoPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatchKt { public static field hookNavigationButtonCreated Lkotlin/jvm/functions/Function1; public static final fun getHookNavigationButtonCreated ()Lkotlin/jvm/functions/Function1; diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt index 148427a95..f60b0354a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt @@ -9,8 +9,8 @@ import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.patches.youtube.shared.autoRepeatFingerprint -import app.revanced.patches.youtube.shared.autoRepeatParentFingerprint +import app.revanced.patches.youtube.shared.loopVideoFingerprint +import app.revanced.patches.youtube.shared.loopVideoParentFingerprint import app.revanced.util.addInstructionsAtControlFlowLabel @Suppress("unused") @@ -50,7 +50,7 @@ internal val exitFullscreenPatch = bytecodePatch( ListPreference("revanced_exit_fullscreen") ) - autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.apply { + loopVideoFingerprint.match(loopVideoParentFingerprint.originalClassDef).method.apply { addInstructionsAtControlFlowLabel( implementation!!.instructions.lastIndex, "invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached()V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt index 324006c05..655f6d176 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatch.kt @@ -1,62 +1,9 @@ package app.revanced.patches.youtube.misc.autorepeat -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.InstructionExtensions.instructions -import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.bytecodePatch -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.settings.PreferenceScreen -import app.revanced.patches.youtube.shared.autoRepeatFingerprint -import app.revanced.patches.youtube.shared.autoRepeatParentFingerprint +import app.revanced.patches.youtube.misc.loopvideo.loopVideoPatch -// TODO: Rename this patch to AlwaysRepeatPatch (as well as strings and references in the extension). -val autoRepeatPatch = bytecodePatch( - name = "Always repeat", - description = "Adds an option to always repeat videos when they end.", -) { - dependsOn( - sharedExtensionPatch, - addResourcesPatch, - ) - - compatibleWith( - "com.google.android.youtube"( - "19.34.42", - "19.43.41", - "20.07.39", - "20.13.41", - "20.14.43", - ) - ) - - execute { - addResources("youtube", "misc.autorepeat.autoRepeatPatch") - - PreferenceScreen.MISC.addPreferences( - SwitchPreference("revanced_auto_repeat"), - ) - - autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.apply { - val playMethod = autoRepeatParentFingerprint.method - val index = instructions.lastIndex - - // Remove return-void. - removeInstruction(index) - // Add own instructions there. - addInstructionsWithLabels( - index, - """ - invoke-static {}, Lapp/revanced/extension/youtube/patches/AutoRepeatPatch;->shouldAutoRepeat()Z - move-result v0 - if-eqz v0, :noautorepeat - invoke-virtual { p0 }, $playMethod - :noautorepeat - return-void - """, - ) - } - } +@Deprecated("Patch was renamed", ReplaceWith("looVideoPatch")) +val autoRepeatPatch = bytecodePatch { + dependsOn(loopVideoPatch) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt new file mode 100644 index 000000000..2aebe318f --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/LoopVideoPatch.kt @@ -0,0 +1,62 @@ +package app.revanced.patches.youtube.misc.loopvideo + +import app.revanced.patcher.patch.bytecodePatch +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.loopvideo.button.loopVideoButtonPatch +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.shared.loopVideoFingerprint +import app.revanced.patches.youtube.shared.loopVideoParentFingerprint +import app.revanced.util.addInstructionsAtControlFlowLabel +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import com.android.tools.smali.dexlib2.Opcode + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/LoopVideoPatch;" + +val loopVideoPatch = bytecodePatch( + name = "Loop video", + description = "Adds an option to loop videos and display loop video button in the video player.", +) { + dependsOn( + sharedExtensionPatch, + addResourcesPatch, + loopVideoButtonPatch + ) + + compatibleWith( + "com.google.android.youtube"( + "19.34.42", + "19.43.41", + "20.07.39", + "20.13.41", + "20.14.43", + ) + ) + + execute { + addResources("youtube", "misc.loopvideo.loopVideoPatch") + + PreferenceScreen.MISC.addPreferences( + SwitchPreference("revanced_loop_video"), + ) + + loopVideoFingerprint.match(loopVideoParentFingerprint.originalClassDef).method.apply { + val playMethod = loopVideoParentFingerprint.method + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_VOID) + + addInstructionsAtControlFlowLabel( + insertIndex, + """ + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldLoopVideo()Z + move-result v0 + if-eqz v0, :do_not_loop + invoke-virtual { p0 }, $playMethod + :do_not_loop + nop + """ + ) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/button/LoopVideoButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/button/LoopVideoButtonPatch.kt new file mode 100644 index 000000000..0c3b9dc68 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/loopvideo/button/LoopVideoButtonPatch.kt @@ -0,0 +1,57 @@ +package app.revanced.patches.youtube.misc.loopvideo.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.util.ResourceGroup +import app.revanced.util.copyResources + +private val loopVideoButtonResourcePatch = resourcePatch { + dependsOn(playerControlsResourcePatch) + + execute { + copyResources( + "loopvideobutton", + ResourceGroup( + "drawable", + "revanced_loop_video_button_on.xml", + "revanced_loop_video_button_off.xml" + ) + ) + + addBottomControl("loopvideobutton") + } +} + +private const val LOOP_VIDEO_BUTTON_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/videoplayer/LoopVideoButton;" + +internal val loopVideoButtonPatch = bytecodePatch( + description = "Adds the option to display loop video button in the video player.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + loopVideoButtonResourcePatch, + playerControlsPatch, + ) + + execute { + addResources("youtube", "misc.loopvideo.button.loopVideoButtonPatch") + + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("revanced_loop_video_button"), + ) + + // Initialize the button using standard approach. + initializeBottomControl(LOOP_VIDEO_BUTTON_CLASS_DESCRIPTOR) + injectVisibilityCheckCall(LOOP_VIDEO_BUTTON_CLASS_DESCRIPTOR) + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt index 6fc138fc5..44c212e6f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt @@ -21,7 +21,10 @@ internal val conversionContextFingerprintToString = fingerprint { } } -internal val autoRepeatFingerprint = fingerprint { +/** + * Resolves to class found in [loopVideoParentFingerprint]. + */ +internal val loopVideoFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("V") parameters() @@ -30,7 +33,7 @@ internal val autoRepeatFingerprint = fingerprint { } } -internal val autoRepeatParentFingerprint = fingerprint { +internal val loopVideoParentFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("V") strings( diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 531f6b115..4a02b6351 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1526,10 +1526,17 @@ Tap here to learn more about DeArrow" Your watch history is not being saved.<br><br>This most likely is caused by a DNS ad blocker or network proxy.<br><br>To fix this, whitelist <b>s.youtube.com</b> or turn off all DNS blockers and proxies. Do not show again - - Enable auto-repeat - Auto-repeat is enabled - Auto-repeat is disabled + + Enable loop video + Video will loop + Video will not loop + + + Show loop video button + Button is shown + Button is not shown + Loop video is on + Loop video is off Spoof device dimensions diff --git a/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_off.xml b/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_off.xml new file mode 100644 index 000000000..e6c5fad66 --- /dev/null +++ b/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_off.xml @@ -0,0 +1,11 @@ + + + diff --git a/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_on.xml b/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_on.xml new file mode 100644 index 000000000..f877626ed --- /dev/null +++ b/patches/src/main/resources/loopvideobutton/drawable/revanced_loop_video_button_on.xml @@ -0,0 +1,9 @@ + + + diff --git a/patches/src/main/resources/loopvideobutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/loopvideobutton/host/layout/youtube_controls_bottom_ui_container.xml new file mode 100644 index 000000000..bcd7def9e --- /dev/null +++ b/patches/src/main/resources/loopvideobutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -0,0 +1,22 @@ + + + + + +