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 @@
+
+
+
+
+
+