fix(YouTube - Settings): Use an overlay to show search results (#5806)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
co-authored by
LisoUseInAIKyrios
parent
ebb446b22a
commit
ece8076f7c
@@ -1,6 +1,5 @@
|
||||
package app.revanced.patches.music.misc.settings
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||
@@ -10,20 +9,19 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.shared.misc.settings.settingsPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.patches.youtube.misc.settings.modifyActivityForSettingsInjection
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/shared/settings/BaseActivityHook;"
|
||||
private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/music/settings/GoogleApiActivityHook;"
|
||||
"Lapp/revanced/extension/music/settings/MusicActivityHook;"
|
||||
|
||||
private val preferences = mutableSetOf<BasePreference>()
|
||||
|
||||
@@ -31,26 +29,19 @@ private val settingsResourcePatch = resourcePatch {
|
||||
dependsOn(
|
||||
resourceMappingPatch,
|
||||
settingsPatch(
|
||||
IntentPreference(
|
||||
titleKey = "revanced_settings_title",
|
||||
summaryKey = null,
|
||||
intent = newIntent("revanced_settings_intent"),
|
||||
) to "settings_headers",
|
||||
listOf(
|
||||
IntentPreference(
|
||||
titleKey = "revanced_settings_title",
|
||||
summaryKey = null,
|
||||
intent = newIntent("revanced_settings_intent"),
|
||||
) to "settings_headers",
|
||||
),
|
||||
preferences
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
// TODO: Remove this when search will be abstract.
|
||||
copyResources(
|
||||
"settings",
|
||||
ResourceGroup(
|
||||
"layout",
|
||||
"revanced_music_settings_with_toolbar.xml"
|
||||
)
|
||||
)
|
||||
|
||||
val targetResource = "values/styles.xml"
|
||||
inputStreamFromBundledResource(
|
||||
"settings/music",
|
||||
@@ -98,24 +89,25 @@ val settingsPatch = bytecodePatch(
|
||||
selectable = true,
|
||||
)
|
||||
|
||||
// Modify GoogleApiActivity and remove all existing layout code.
|
||||
// Must modify an existing activity and cannot add a new activity to the manifest,
|
||||
// as that fails for root installations.
|
||||
|
||||
googleApiActivityFingerprint.method.addInstructions(
|
||||
1,
|
||||
"""
|
||||
invoke-static { }, $GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR->createInstance()Lapp/revanced/extension/music/settings/GoogleApiActivityHook;
|
||||
move-result-object v0
|
||||
invoke-static { v0, p0 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Lapp/revanced/extension/shared/settings/BaseActivityHook;Landroid/app/Activity;)V
|
||||
return-void
|
||||
"""
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
SwitchPreference("revanced_settings_search_history")
|
||||
)
|
||||
|
||||
// Remove other methods as they will break as the onCreate method is modified above.
|
||||
googleApiActivityFingerprint.classDef.apply {
|
||||
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
|
||||
}
|
||||
PreferenceScreen.MISC.addPreferences(
|
||||
TextPreference(
|
||||
key = null,
|
||||
titleKey = "revanced_pref_import_export_title",
|
||||
summaryKey = "revanced_pref_import_export_summary",
|
||||
inputType = InputType.TEXT_MULTI_LINE,
|
||||
tag = "app.revanced.extension.shared.settings.preference.ImportExportPreference",
|
||||
)
|
||||
)
|
||||
|
||||
modifyActivityForSettingsInjection(
|
||||
googleApiActivityFingerprint.classDef,
|
||||
googleApiActivityFingerprint.method,
|
||||
GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR
|
||||
)
|
||||
}
|
||||
|
||||
finalize {
|
||||
|
||||
@@ -65,14 +65,15 @@ fun settingsPatch (
|
||||
copyResources(
|
||||
"settings",
|
||||
ResourceGroup("xml", "revanced_prefs.xml", "revanced_prefs_icons.xml"),
|
||||
ResourceGroup("menu", "revanced_search_menu.xml"),
|
||||
ResourceGroup("drawable",
|
||||
// CustomListPreference resources.
|
||||
"revanced_ic_dialog_alert.xml",
|
||||
"revanced_settings_arrow_time.xml",
|
||||
"revanced_settings_circle_background.xml",
|
||||
"revanced_settings_cursor.xml",
|
||||
"revanced_settings_custom_checkmark.xml",
|
||||
"revanced_settings_search_icon.xml",
|
||||
"revanced_settings_search_remove.xml",
|
||||
"revanced_settings_toolbar_arrow_left.xml",
|
||||
),
|
||||
ResourceGroup("layout",
|
||||
@@ -80,6 +81,16 @@ fun settingsPatch (
|
||||
// Color picker.
|
||||
"revanced_color_dot_widget.xml",
|
||||
"revanced_color_picker.xml",
|
||||
// Search.
|
||||
"revanced_preference_search_history_item.xml",
|
||||
"revanced_preference_search_history_screen.xml",
|
||||
"revanced_preference_search_no_result.xml",
|
||||
"revanced_preference_search_result_color.xml",
|
||||
"revanced_preference_search_result_group_header.xml",
|
||||
"revanced_preference_search_result_list.xml",
|
||||
"revanced_preference_search_result_regular.xml",
|
||||
"revanced_preference_search_result_switch.xml",
|
||||
"revanced_settings_with_toolbar.xml"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/tiktok/settings/AdPersonalizationActivityHook;"
|
||||
"Lapp/revanced/extension/tiktok/settings/TikTokActivityHook;"
|
||||
|
||||
val settingsPatch = bytecodePatch(
|
||||
name = "Settings",
|
||||
|
||||
@@ -29,7 +29,7 @@ private const val MENU_DISMISS_EVENT_CLASS_DESCRIPTOR =
|
||||
"Ltv/twitch/android/feature/settings/menu/SettingsMenuViewDelegate\$Event\$OnDismissClicked;"
|
||||
|
||||
private const val EXTENSION_PACKAGE = "app/revanced/extension/twitch"
|
||||
private const val ACTIVITY_HOOKS_CLASS_DESCRIPTOR = "L$EXTENSION_PACKAGE/settings/AppCompatActivityHook;"
|
||||
private const val ACTIVITY_HOOKS_CLASS_DESCRIPTOR = "L$EXTENSION_PACKAGE/settings/TwitchActivityHook;"
|
||||
private const val UTILS_CLASS_DESCRIPTOR = "L$EXTENSION_PACKAGE/Utils;"
|
||||
|
||||
private val preferences = mutableSetOf<BasePreference>()
|
||||
|
||||
+2
-2
@@ -46,10 +46,10 @@ private val swipeControlsResourcePatch = resourcePatch {
|
||||
ListPreference("revanced_swipe_overlay_style"),
|
||||
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
|
||||
TextPreference("revanced_swipe_overlay_progress_brightness_color",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerWithOpacitySliderPreference",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
TextPreference("revanced_swipe_overlay_progress_volume_color",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerWithOpacitySliderPreference",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER),
|
||||
TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER),
|
||||
|
||||
+8
-2
@@ -189,7 +189,10 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
|
||||
SwitchPreference("revanced_hide_keyword_content_search"),
|
||||
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
||||
NonInteractivePreference("revanced_hide_keyword_content_about"),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference"
|
||||
),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about_whole_words",
|
||||
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
|
||||
@@ -223,7 +226,10 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_expandable_card"),
|
||||
SwitchPreference("revanced_hide_floating_microphone_button"),
|
||||
SwitchPreference("revanced_hide_horizontal_shelves"),
|
||||
SwitchPreference(
|
||||
key = "revanced_hide_horizontal_shelves",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference"
|
||||
),
|
||||
SwitchPreference("revanced_hide_image_shelf"),
|
||||
SwitchPreference("revanced_hide_latest_posts"),
|
||||
SwitchPreference("revanced_hide_mix_playlists"),
|
||||
|
||||
+87
-102
@@ -4,6 +4,8 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
@@ -31,8 +33,8 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/shared/settings/BaseActivityHook;"
|
||||
private const val LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/settings/LicenseActivityHook;"
|
||||
private const val YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/settings/YouTubeActivityHook;"
|
||||
|
||||
internal var appearanceStringId = -1L
|
||||
private set
|
||||
@@ -73,7 +75,8 @@ private val settingsResourcePatch = resourcePatch {
|
||||
// Use same colors as stock YouTube.
|
||||
overrideThemeColors("@color/yt_white1", "@color/yt_black3")
|
||||
|
||||
arrayOf(
|
||||
copyResources(
|
||||
"settings",
|
||||
ResourceGroup("drawable",
|
||||
"revanced_settings_icon.xml",
|
||||
"revanced_settings_screen_00_about.xml",
|
||||
@@ -89,23 +92,15 @@ private val settingsResourcePatch = resourcePatch {
|
||||
"revanced_settings_screen_10_sponsorblock.xml",
|
||||
"revanced_settings_screen_11_misc.xml",
|
||||
"revanced_settings_screen_12_video.xml",
|
||||
),
|
||||
ResourceGroup("layout",
|
||||
"revanced_preference_with_icon_no_search_result.xml",
|
||||
"revanced_search_suggestion_item.xml",
|
||||
"revanced_settings_with_toolbar.xml"
|
||||
),
|
||||
ResourceGroup("menu", "revanced_search_menu.xml")
|
||||
).forEach { resourceGroup ->
|
||||
copyResources("settings", resourceGroup)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Copy style properties used to fix over-sized copy menu that appear in EditTextPreference.
|
||||
// For a full explanation of how this fixes the issue, see the comments in this style file
|
||||
// and the comments in the extension code.
|
||||
val targetResource = "values/styles.xml"
|
||||
inputStreamFromBundledResource(
|
||||
"settings/host",
|
||||
"settings/youtube",
|
||||
targetResource,
|
||||
)!!.let { inputStream ->
|
||||
"resources".copyXmlNode(
|
||||
@@ -215,92 +210,6 @@ val settingsPatch = bytecodePatch(
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
// Modify the license activity and remove all existing layout code.
|
||||
// Must modify an existing activity and cannot add a new activity to the manifest,
|
||||
// as that fails for root installations.
|
||||
|
||||
licenseActivityOnCreateFingerprint.method.addInstructions(
|
||||
1,
|
||||
"""
|
||||
invoke-static {}, $LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->createInstance()Lapp/revanced/extension/youtube/settings/LicenseActivityHook;
|
||||
move-result-object v0
|
||||
invoke-static { v0, p0 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Lapp/revanced/extension/shared/settings/BaseActivityHook;Landroid/app/Activity;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
|
||||
// Remove other methods as they will break as the onCreate method is modified above.
|
||||
licenseActivityOnCreateFingerprint.classDef.apply {
|
||||
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
|
||||
}
|
||||
|
||||
licenseActivityOnCreateFingerprint.classDef.apply {
|
||||
// Add attachBaseContext method to override the context for setting a specific language.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"attachBaseContext",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)),
|
||||
"V",
|
||||
AccessFlags.PROTECTED.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static { p1 }, $LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context;
|
||||
move-result-object p1
|
||||
invoke-super { p0, p1 }, $superclass->attachBaseContext(Landroid/content/Context;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
|
||||
// Add onBackPressed method to handle back button presses, delegating to SearchViewController.
|
||||
ImmutableMethod(
|
||||
type,
|
||||
"onBackPressed",
|
||||
emptyList(),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/extension/youtube/settings/SearchViewController;->handleBackPress()Z
|
||||
move-result v0
|
||||
if-nez v0, :search_handled
|
||||
invoke-virtual { p0 }, Landroid/app/Activity;->finish()V
|
||||
:search_handled
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(methods::add)
|
||||
|
||||
// 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 }, $LICENSE_ACTIVITY_HOOK_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
|
||||
// which then differs from the system dark mode status.
|
||||
@@ -309,7 +218,7 @@ val settingsPatch = bytecodePatch(
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
addInstructionsAtControlFlowLabel(
|
||||
index,
|
||||
"invoke-static { v$register }, ${LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR}->updateLightDarkModeStatus(Ljava/lang/Enum;)V",
|
||||
"invoke-static { v$register }, ${YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR}->updateLightDarkModeStatus(Ljava/lang/Enum;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -317,7 +226,13 @@ val settingsPatch = bytecodePatch(
|
||||
// Add setting to force Cairo settings fragment on/off.
|
||||
cairoFragmentConfigFingerprint.method.insertLiteralOverride(
|
||||
CAIRO_CONFIG_LITERAL_VALUE,
|
||||
"$LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z"
|
||||
"$YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z"
|
||||
)
|
||||
|
||||
modifyActivityForSettingsInjection(
|
||||
licenseActivityOnCreateFingerprint.classDef,
|
||||
licenseActivityOnCreateFingerprint.method,
|
||||
YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR
|
||||
)
|
||||
}
|
||||
|
||||
@@ -326,6 +241,76 @@ val settingsPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the activity to show ReVanced settings instead of it's original purpose.
|
||||
*/
|
||||
internal fun modifyActivityForSettingsInjection(
|
||||
activityOnCreateClass: MutableClass,
|
||||
activityOnCreateMethod: MutableMethod,
|
||||
extensionClassType: String
|
||||
) {
|
||||
// Modify Activity and remove all existing layout code.
|
||||
// Must modify an existing activity and cannot add a new activity to the manifest,
|
||||
// as that fails for root installations.
|
||||
activityOnCreateMethod.addInstructions(
|
||||
1,
|
||||
"""
|
||||
invoke-static { p0 }, $extensionClassType->initialize(Landroid/app/Activity;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
|
||||
// Remove other methods as they will break as the onCreate method is modified above.
|
||||
activityOnCreateClass.apply {
|
||||
methods.removeIf { it != activityOnCreateMethod && !MethodUtil.isConstructor(it) }
|
||||
}
|
||||
|
||||
// Override base context to allow using ReVanced specific settings.
|
||||
ImmutableMethod(
|
||||
activityOnCreateClass.type,
|
||||
"attachBaseContext",
|
||||
listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)),
|
||||
"V",
|
||||
AccessFlags.PROTECTED.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static { p1 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context;
|
||||
move-result-object p1
|
||||
invoke-super { p0, p1 }, ${activityOnCreateClass.superclass}->attachBaseContext(Landroid/content/Context;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(activityOnCreateClass.methods::add)
|
||||
|
||||
// Override finish() to intercept back gesture.
|
||||
ImmutableMethod(
|
||||
activityOnCreateClass.type,
|
||||
"finish",
|
||||
emptyList(),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static {}, $extensionClassType->handleFinish()Z
|
||||
move-result v0
|
||||
if-nez v0, :search_handled
|
||||
invoke-super { p0 }, Landroid/app/Activity;->finish()V
|
||||
return-void
|
||||
:search_handled
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}.let(activityOnCreateClass.methods::add)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an intent to open ReVanced settings.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ internal val settingsMenuVideoSpeedGroup = mutableSetOf<BasePreference>()
|
||||
@Suppress("unused")
|
||||
val playbackSpeedPatch = bytecodePatch(
|
||||
name = "Playback speed",
|
||||
description = "Adds options to customize available playback speeds, set default a playback speed, " +
|
||||
description = "Adds options to customize available playback speeds, set a default playback speed, " +
|
||||
"and show a speed dialog button in the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
|
||||
Reference in New Issue
Block a user