feat(YouTube - Loop video): Add player button to change loop video state (#5961)

This commit is contained in:
MarcaD
2025-09-22 11:54:09 +03:00
committed by GitHub
parent 6d5f6ecdd2
commit dfb5407e67
14 changed files with 299 additions and 89 deletions

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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
"""
)
}
}
}

View File

@@ -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)
}
}

View File

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

View File

@@ -1526,10 +1526,17 @@ Tap here to learn more about DeArrow"</string>
<string name="revanced_check_watch_history_domain_name_dialog_message">Your watch history is not being saved.&lt;br>&lt;br>This most likely is caused by a DNS ad blocker or network proxy.&lt;br>&lt;br>To fix this, whitelist &lt;b>s.youtube.com&lt;/b> or turn off all DNS blockers and proxies.</string>
<string name="revanced_check_watch_history_domain_name_dialog_ignore">Do not show again</string>
</patch>
<patch id="misc.autorepeat.autoRepeatPatch">
<string name="revanced_auto_repeat_title">Enable auto-repeat</string>
<string name="revanced_auto_repeat_summary_on">Auto-repeat is enabled</string>
<string name="revanced_auto_repeat_summary_off">Auto-repeat is disabled</string>
<patch id="misc.loopvideo.loopVideoPatch">
<string name="revanced_loop_video_title">Enable loop video</string>
<string name="revanced_loop_video_summary_on">Video will loop</string>
<string name="revanced_loop_video_summary_off">Video will not loop</string>
</patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
<string name="revanced_loop_video_button_title">Show loop video button</string>
<string name="revanced_loop_video_button_summary_on">Button is shown</string>
<string name="revanced_loop_video_button_summary_off">Button is not shown</string>
<string name="revanced_loop_video_button_toast_on">Loop video is on</string>
<string name="revanced_loop_video_button_toast_off">Loop video is off</string>
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
<string name="revanced_spoof_device_dimensions_title">Spoof device dimensions</string>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:fillAlpha="0.6"
android:strokeAlpha="0.6"
android:pathData="M17.6914,2.19922 L16.9844,2.91797 L19.0625,5 L3.61719,5 C3.15625,5,2.76953,5.14844,2.46094,5.46094 C2.15234,5.76562,2,6.15234,2,6.61719 L2,10 L3,10 L3,6.61719 C3,6.43359,3.05859,6.28516,3.17188,6.17188 C3.28906,6.05859,3.4375,6,3.61719,6 L19.0703,6 L19.0703,6.00781 L16.9844,8.09375 L17.6914,8.8125 L21,5.50781 L17.6914,2.19922 Z M21,14 L21,17.3828 C21,17.5625,20.9414,17.7109,20.8281,17.8281 C20.7109,17.9414,20.5625,18,20.3828,18 L4.92969,18 L4.92969,17.9922 L7.01563,15.9023 L6.30859,15.1836 L3,18.4922 L6.30859,21.7969 L7.01563,21.0781 L4.9375,19 L20.3828,19 C20.8438,19,21.2305,18.8477,21.5391,18.5391 C21.8477,18.2305,22,17.8438,22,17.3828 L22,14 L21,14 Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M17.6914,2.19922 L16.9844,2.91797 L19.0625,5 L3.61719,5 C3.15625,5,2.76953,5.14844,2.46094,5.46094 C2.15234,5.76562,2,6.15234,2,6.61719 L2,10 L3,10 L3,6.61719 C3,6.43359,3.05859,6.28516,3.17188,6.17188 C3.28906,6.05859,3.4375,6,3.61719,6 L19.0703,6 L19.0703,6.00781 L16.9844,8.09375 L17.6914,8.8125 L21,5.50781 L17.6914,2.19922 Z M12.7168,8.30078 L10.4609,9.28906 L10.4609,10.1152 L11.9746,9.44336 L11.9746,15.543 L12.8496,15.543 L12.8496,8.30078 L12.7168,8.30078 Z M21,14 L21,17.3828 C21,17.5625,20.9414,17.7109,20.8281,17.8281 C20.7109,17.9414,20.5625,18,20.3828,18 L4.92969,18 L4.92969,17.9922 L7.01563,15.9023 L6.30859,15.1836 L3,18.4922 L6.30859,21.7969 L7.01563,21.0781 L4.9375,19 L20.3828,19 C20.8438,19,21.2305,18.8477,21.5391,18.5391 C21.8477,18.2305,22,17.8438,22,17.3828 L22,14 L21,14 Z" />
</vector>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:yt="http://schemas.android.com/apk/res-auto"
android:id="@+id/youtube_controls_bottom_ui_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_loop_video_button"
style="@style/YouTubePlayerButton"
android:layout_width="48.0dip"
android:layout_height="60.0dip"
android:paddingTop="6.0dp"
android:paddingBottom="0dp"
android:scaleType="center"
android:src="@drawable/revanced_loop_video_button_off"
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
</android.support.constraint.ConstraintLayout>