Merge branch 'refs/heads/master' into landscape

# Conflicts:
#	app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt
#	app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt
This commit is contained in:
Kai 2024-09-11 21:37:34 -05:00
commit 74efec3235
No known key found for this signature in database
13 changed files with 131 additions and 80 deletions

View file

@ -470,15 +470,22 @@ class Settings : FragmentedStorageFileJson() {
@DropdownFieldOptionsId(R.array.rotation_zone) @DropdownFieldOptionsId(R.array.rotation_zone)
var rotationZone: Int = 2; var rotationZone: Int = 2;
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 16) @FormField(R.string.stability_threshold_time, FieldForm.DROPDOWN, R.string.stability_threshold_time_description, 16)
@DropdownFieldOptionsId(R.array.rotation_threshold_time)
var stabilityThresholdTime: Int = 1;
@FormField(R.string.full_autorotate_lock, FieldForm.TOGGLE, R.string.full_autorotate_lock_description, 17)
var fullAutorotateLock: Boolean = false;
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 18)
var preferWebmVideo: Boolean = false; var preferWebmVideo: Boolean = false;
@FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 17) @FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 19)
var preferWebmAudio: Boolean = false; var preferWebmAudio: Boolean = false;
@FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 18) @FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 20)
var allowVideoToGoUnderCutout: Boolean = true; var allowVideoToGoUnderCutout: Boolean = true;
@FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 19) @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 21)
var autoplay: Boolean = false; var autoplay: Boolean = false;
} }

View file

@ -237,7 +237,8 @@ open class JSClient : IPlatformClient {
hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false, hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false,
hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false, hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false,
hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false, hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false,
hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false,
hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false
); );
try { try {

View file

@ -216,26 +216,30 @@ class MenuBottomBarFragment : MainActivityFragment() {
_moreButtons.clear(); _moreButtons.clear();
_layoutMoreButtons.removeAllViews(); _layoutMoreButtons.removeAllViews();
var insertedButtons = 0;
//Force buy to be on top for more buttons //Force buy to be on top for more buttons
val buyIndex = buttons.indexOfFirst { b -> b.id == 98 }; val buyIndex = buttons.indexOfFirst { b -> b.id == 98 };
if (buyIndex != -1) { if (buyIndex != -1) {
val button = buttons[buyIndex] val button = buttons[buyIndex]
buttons.removeAt(buyIndex) buttons.removeAt(buyIndex)
buttons.add(0, button) buttons.add(0, button)
insertedButtons++;
} }
//Force faq to be second //Force faq to be second
val faqIndex = buttons.indexOfFirst { b -> b.id == 97 }; val faqIndex = buttons.indexOfFirst { b -> b.id == 97 };
if (faqIndex != -1) { if (faqIndex != -1) {
val button = buttons[faqIndex] val button = buttons[faqIndex]
buttons.removeAt(faqIndex) buttons.removeAt(faqIndex)
buttons.add(if (buttons.size == 1) 1 else 0, button) buttons.add(if (insertedButtons == 1) 1 else 0, button)
insertedButtons++;
} }
//Force privacy to be third //Force privacy to be third
val privacyIndex = buttons.indexOfFirst { b -> b.id == 96 }; val privacyIndex = buttons.indexOfFirst { b -> b.id == 96 };
if (privacyIndex != -1) { if (privacyIndex != -1) {
val button = buttons[privacyIndex] val button = buttons[privacyIndex]
buttons.removeAt(privacyIndex) buttons.removeAt(privacyIndex)
buttons.add(if (buttons.size == 2) 2 else 1, button) buttons.add(if (insertedButtons == 2) 2 else (if(insertedButtons == 1) 1 else 0), button)
insertedButtons++;
} }
for (data in buttons) { for (data in buttons) {
@ -327,19 +331,6 @@ class MenuBottomBarFragment : MainActivityFragment() {
if (!StatePayment.instance.hasPaid) { if (!StatePayment.instance.hasPaid) {
newCurrentButtonDefinitions.add(ButtonDefinition(98, R.drawable.ic_paid, R.drawable.ic_paid_filled, R.string.buy, canToggle = false, { it.currentMain is BuyFragment }, { it.navigate<BuyFragment>() })) newCurrentButtonDefinitions.add(ButtonDefinition(98, R.drawable.ic_paid, R.drawable.ic_paid_filled, R.string.buy, canToggle = false, { it.currentMain is BuyFragment }, { it.navigate<BuyFragment>() }))
} }
newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, {
it.navigate<BrowserFragment>(Settings.URL_FAQ);
}))
newCurrentButtonDefinitions.add(ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = false, { false }, {
UIDialogs.showDialog(context, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
"All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0,
UIDialogs.Action("Cancel", {
StateApp.instance.setPrivacyMode(false);
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Enable", {
StateApp.instance.setPrivacyMode(true);
}, UIDialogs.ActionStyle.PRIMARY));
}))
//Add conditional buttons here, when you add a conditional button, be sure to add the register and unregister events for when the button needs to be updated //Add conditional buttons here, when you add a conditional button, be sure to add the register and unregister events for when the button needs to be updated
@ -412,6 +403,19 @@ class MenuBottomBarFragment : MainActivityFragment() {
if (c is Activity) { if (c is Activity) {
c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken); c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken);
} }
}),
ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, {
UIDialogs.showDialog(it.context ?: return@ButtonDefinition, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
"All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0,
UIDialogs.Action("Cancel", {
StateApp.instance.setPrivacyMode(false);
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Enable", {
StateApp.instance.setPrivacyMode(true);
}, UIDialogs.ActionStyle.PRIMARY));
}),
ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, {
it.navigate<BrowserFragment>(Settings.URL_FAQ);
}) })
//96 is reserved for privacy button //96 is reserved for privacy button
//98 is reserved for buy button //98 is reserved for buy button

View file

@ -397,23 +397,43 @@ class SourceDetailFragment : MainFragment() {
UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE), UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Login", { UIDialogs.Action("Login", {
LoginActivity.showLogin(StateApp.instance.context, config) { LoginActivity.showLogin(StateApp.instance.context, config) {
try {
StatePlugins.instance.setPluginAuth(config.id, it); StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id); reloadSource(config.id);
} catch (e: Throwable) {
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource, loginWarning)", e) }
}
Logger.e(TAG, "Failed to set plugin authentication (loginSource, loginWarning)", e)
}
}; };
}, UIDialogs.ActionStyle.PRIMARY)) }, UIDialogs.ActionStyle.PRIMARY))
} }
else else
LoginActivity.showLogin(StateApp.instance.context, config) { LoginActivity.showLogin(StateApp.instance.context, config) {
try {
StatePlugins.instance.setPluginAuth(config.id, it); StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id); reloadSource(config.id);
} catch (e: Throwable) {
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource)", e) }
}
Logger.e(TAG, "Failed to set plugin authentication (loginSource)", e)
}
}; };
} }
private fun logoutSource(clear: Boolean = true) { private fun logoutSource(clear: Boolean = true) {
val config = _config ?: return; val config = _config ?: return;
try {
StatePlugins.instance.setPluginAuth(config.id, null); StatePlugins.instance.setPluginAuth(config.id, null);
reloadSource(config.id); reloadSource(config.id);
} catch (e: Throwable) {
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to clear plugin authentication", e) }
}
Logger.e(TAG, "Failed to clear plugin authentication", e)
}
//TODO: Maybe add a dialog option.. //TODO: Maybe add a dialog option..
if(Settings.instance.plugins.clearCookiesOnLogout && clear) { if(Settings.instance.plugins.clearCookiesOnLogout && clear) {

View file

@ -116,17 +116,18 @@ class VideoDetailFragment : MainFragment {
fun updateOrientation() { fun updateOrientation() {
val a = activity ?: return val a = activity ?: return
// only applies to small windows // only applies to small windows
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait; val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait
val isReversePortraitAllowed = Settings.instance.playback.reversePortrait; val isReversePortraitAllowed = Settings.instance.playback.reversePortrait
val fullAutorotateLock = Settings.instance.playback.fullAutorotateLock
val rotationLock = StatePlayer.instance.rotationLock val rotationLock = StatePlayer.instance.rotationLock
// For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape // For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape
if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock) { if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} }
// For small windows if the device isn't in a portrait orientation and we're in the maximized state then we should force portrait // For small windows if the device isn't in a portrait orientation and we're in the maximized state then we should force portrait
else if (isSmallWindow && !isMinimizing && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { else if (isSmallWindow && !isMinimizing && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
} else if (rotationLock) { } else if (rotationLock) {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
} else { } else {

View file

@ -2364,20 +2364,11 @@ class VideoDetailView : ConstraintLayout {
_layoutRecommended.visibility = View.VISIBLE _layoutRecommended.visibility = View.VISIBLE
_commentsList.clear() _commentsList.clear()
val url = _url
if (url != null) {
_layoutRecommended.addView(LoaderView(context).apply { _layoutRecommended.addView(LoaderView(context).apply {
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources)) layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
start() start()
}) })
_taskLoadRecommendations.run(url) _taskLoadRecommendations.run(null)
} else {
_layoutRecommended.addView(TextView(context).apply {
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
textSize = 12.0f
text = "No recommendations found"
})
}
} }
} }
@ -2389,7 +2380,7 @@ class VideoDetailView : ConstraintLayout {
if (_tabIndex == 2) { if (_tabIndex == 2) {
_layoutRecommended.removeAllViews() _layoutRecommended.removeAllViews()
if (results == null) { if (results == null || results.isEmpty()) {
_layoutRecommended.addView(TextView(context).apply { _layoutRecommended.addView(TextView(context).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources)) setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
@ -2787,7 +2778,15 @@ class VideoDetailView : ConstraintLayout {
} }
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
private val _taskLoadRecommendations = TaskHandler<String, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) }) private val _taskLoadRecommendations = TaskHandler<String?, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, {
video?.let { v ->
if (v is VideoLocal) {
StatePlatform.instance.getContentRecommendations(v.url)
} else {
video?.getContentRecommendations(StatePlatform.instance.getContentClient(v.url))
}
}
})
.success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") } .success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") }
.exception<Throwable> { .exception<Throwable> {
setRecommendations(null, it.message) setRecommendations(null, it.message)

View file

@ -61,8 +61,17 @@ class StatePlaylists {
} }
fun updateWatchLater(updated: List<SerializedPlatformVideo>) { fun updateWatchLater(updated: List<SerializedPlatformVideo>) {
synchronized(_watchlistStore) { synchronized(_watchlistStore) {
_watchlistStore.deleteAll(); //_watchlistStore.deleteAll();
_watchlistStore.saveAllAsync(updated); val existing = _watchlistStore.getItems();
val toAdd = updated.filter { u -> !existing.any { u.url == it.url } };
val toRemove = existing.filter { u -> !updated.any { u.url == it.url } };
Logger.i(TAG, "WatchLater changed:\nTo Add:\n" +
(if(toAdd.size == 0) "None" else toAdd.map { " + " + it.name }.joinToString("\n")) +
"\nTo Remove:\n" +
(if(toRemove.size == 0) "None" else toRemove.map { " - " + it.name }.joinToString("\n")));
for(remove in toRemove)
_watchlistStore.delete(remove);
_watchlistStore.saveAllAsync(toAdd);
_watchlistOrderStore.set(*updated.map { it.url }.toTypedArray()); _watchlistOrderStore.set(*updated.map { it.url }.toTypedArray());
_watchlistOrderStore.save(); _watchlistOrderStore.save();
} }

View file

@ -10,6 +10,8 @@ import com.futo.platformplayer.api.media.platforms.js.SourceAuth
import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment.Companion
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.models.ImageVariable
import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorage
@ -128,7 +130,15 @@ class StatePlugins {
return false; return false;
LoginActivity.showLogin(context, config) { LoginActivity.showLogin(context, config) {
try {
StatePlugins.instance.setPluginAuth(config.id, it); StatePlugins.instance.setPluginAuth(config.id, it);
} catch (e: Throwable) {
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
UIDialogs.showGeneralErrorDialog(context, "Failed to set plugin authentication (loginPlugin)", e)
}
Logger.e(SourceDetailFragment.TAG, "Failed to set plugin authentication (loginPlugin)", e)
return@showLogin
}
StateApp.instance.scope.launch(Dispatchers.IO) { StateApp.instance.scope.launch(Dispatchers.IO) {
StatePlatform.instance.reloadClient(context, id); StatePlatform.instance.reloadClient(context, id);

View file

@ -29,6 +29,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"> app:layout_constraintRight_toRightOf="parent">
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:clickable="true"
android:padding="12dp"
app:srcCompat="@drawable/autoplay_24px" />
<ImageButton <ImageButton
android:id="@+id/button_cast" android:id="@+id/button_cast"
android:layout_width="50dp" android:layout_width="50dp"
@ -133,20 +141,6 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:layout_marginBottom="18dp" /> android:layout_marginBottom="18dp" />
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="55dp"
android:layout_height="40dp"
android:clickable="true"
app:srcCompat="@drawable/autoplay_24px"
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
android:paddingStart="5dp"
android:paddingTop="15dp"
android:paddingEnd="5dp"
android:scaleType="fitCenter"/>
<TextView <TextView
android:id="@+id/text_position" android:id="@+id/text_position"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -57,6 +57,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"> app:layout_constraintRight_toRightOf="parent">
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:clickable="true"
android:padding="12dp"
app:srcCompat="@drawable/autoplay_24px" />
<ImageButton <ImageButton
android:id="@+id/button_cast" android:id="@+id/button_cast"
android:layout_width="50dp" android:layout_width="50dp"
@ -147,20 +155,6 @@
app:layout_constraintLeft_toRightOf="@id/layout_play_pause" app:layout_constraintLeft_toRightOf="@id/layout_play_pause"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="55dp"
android:layout_height="40dp"
android:clickable="true"
app:srcCompat="@drawable/autoplay_24px"
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
android:paddingStart="5dp"
android:paddingTop="15dp"
android:paddingEnd="5dp"
android:scaleType="fitCenter"/>
<ImageButton <ImageButton
android:id="@+id/button_fullscreen" android:id="@+id/button_fullscreen"
android:layout_width="55dp" android:layout_width="55dp"

View file

@ -377,8 +377,12 @@
<string name="reverse_portrait_description">Allow app to flip into reverse portrait</string> <string name="reverse_portrait_description">Allow app to flip into reverse portrait</string>
<string name="rotation_zone">Rotation zone</string> <string name="rotation_zone">Rotation zone</string>
<string name="rotation_zone_description">Specify the sensitivity of rotation zones (decrease to make less sensitive)</string> <string name="rotation_zone_description">Specify the sensitivity of rotation zones (decrease to make less sensitive)</string>
<string name="stability_threshold_time">Stability threshold time</string>
<string name="stability_threshold_time_description">Specify the duration the orientation needs to be the same to trigger a rotation</string>
<string name="prefer_webm">Prefer Webm Video Codecs</string> <string name="prefer_webm">Prefer Webm Video Codecs</string>
<string name="prefer_webm_description">If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility.</string> <string name="prefer_webm_description">If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility.</string>
<string name="full_autorotate_lock">Full auto rotate lock</string>
<string name="full_autorotate_lock_description">Prevent any rotation while rotation lock is engaged (even flipping between landscape and landscape reverse).</string>
<string name="prefer_webm_audio">Prefer Webm Audio Codecs</string> <string name="prefer_webm_audio">Prefer Webm Audio Codecs</string>
<string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string> <string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string>
<string name="allow_under_cutout">Allow video under cutout</string> <string name="allow_under_cutout">Allow video under cutout</string>
@ -966,4 +970,12 @@
<item>30</item> <item>30</item>
<item>45</item> <item>45</item>
</string-array> </string-array>
<string-array name="rotation_threshold_time">
<item>100</item>
<item>500</item>
<item>750</item>
<item>1000</item>
<item>1500</item>
<item>2000</item>
</string-array>
</resources> </resources>

@ -1 +1 @@
Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758 Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d

@ -1 +1 @@
Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758 Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d