Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin 2024-09-10 20:40:17 +02:00
commit c3d7df166b
10 changed files with 158 additions and 88 deletions

View file

@ -478,15 +478,22 @@ class Settings : FragmentedStorageFileJson() {
@DropdownFieldOptionsId(R.array.rotation_zone)
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;
@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;
@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;
@FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 19)
@FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 21)
var autoplay: Boolean = false;
}

View file

@ -8,6 +8,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -17,13 +18,23 @@ class SimpleOrientationListener(
) {
private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
private val stabilityThresholdTime = 500L
private var _currentJob: Job? = null
val onOrientationChanged = Event1<Int>()
private val orientationListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) {
//val rotationZone = 45
val stabilityThresholdTime = when (Settings.instance.playback.stabilityThresholdTime) {
0 -> 100L
1 -> 500L
2 -> 750L
3 -> 1000L
4 -> 1500L
5 -> 2000L
else -> 500L
}
val rotationZone = when (Settings.instance.playback.rotationZone) {
0 -> 15
1 -> 30
@ -42,7 +53,8 @@ class SimpleOrientationListener(
if (newOrientation != lastStableOrientation) {
lastStableOrientation = newOrientation
lifecycleScope.launch(Dispatchers.Main) {
_currentJob?.cancel()
_currentJob = lifecycleScope.launch(Dispatchers.Main) {
try {
delay(stabilityThresholdTime)
if (newOrientation == lastStableOrientation) {
@ -63,6 +75,8 @@ class SimpleOrientationListener(
}
fun stopListening() {
_currentJob?.cancel()
_currentJob = null
orientationListener.disable()
}

View file

@ -310,19 +310,6 @@ class MenuBottomBarFragment : MainActivityFragment() {
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(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
@ -379,13 +366,26 @@ class MenuBottomBarFragment : MainActivityFragment() {
}),
ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate<SubscriptionsFeedFragment>() }),
ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>() }),
ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>() }),
ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = true, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>() }),
ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>() }),
ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }),
ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }),
ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>() }),
ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate<SubscriptionGroupListFragment>() }),
ButtonDefinition(10, R.drawable.ic_help_square, R.drawable.ic_help_square_fill, R.string.tutorials, canToggle = true, { it.currentMain is TutorialFragment }, { it.navigate<TutorialFragment>() }),
ButtonDefinition(11, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, {
it.navigate<BrowserFragment>(Settings.URL_FAQ)
}),
ButtonDefinition(12, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, {
UIDialogs.showDialog(it.requireContext(), 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(7, R.drawable.ic_settings, R.drawable.ic_settings_filled, R.string.settings, canToggle = false, { false }, {
val c = it.context ?: return@ButtonDefinition;
Logger.i(TAG, "settings preventPictureInPicture()");
@ -396,7 +396,6 @@ class MenuBottomBarFragment : MainActivityFragment() {
c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken);
}
})
//96 is reserved for privacy button
//98 is reserved for buy button
//99 is reserved for more button
);

View file

@ -397,23 +397,43 @@ class SourceDetailFragment : MainFragment() {
UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Login", {
LoginActivity.showLogin(StateApp.instance.context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id);
try {
StatePlugins.instance.setPluginAuth(config.id, it);
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))
}
else
LoginActivity.showLogin(StateApp.instance.context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id);
try {
StatePlugins.instance.setPluginAuth(config.id, it);
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) {
val config = _config ?: return;
StatePlugins.instance.setPluginAuth(config.id, null);
reloadSource(config.id);
try {
StatePlugins.instance.setPluginAuth(config.id, null);
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..
if(Settings.instance.plugins.clearCookiesOnLogout && clear) {

View file

@ -97,6 +97,7 @@ class VideoDetailFragment : MainFragment {
val isMaximized = state == State.MAXIMIZED
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait;
val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention;
val fullAutorotateLock = Settings.instance.playback.fullAutorotateLock
val currentRequestedOrientation = a.requestedOrientation
var currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT && !Settings.instance.playback.reversePortrait)
@ -105,27 +106,47 @@ class VideoDetailFragment : MainFragment {
val isAutoRotate = Settings.instance.playback.isAutoRotate()
val isFs = isFullscreen
if (isFs && isMaximized) {
if (isFullScreenPortraitAllowed) {
if (isAutoRotate) {
a.requestedOrientation = currentOrientation
}
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) {
a.requestedOrientation = currentOrientation
if (fullAutorotateLock) {
if (isFs && isMaximized) {
if (isFullScreenPortraitAllowed) {
if (isAutoRotate) {
a.requestedOrientation = currentOrientation
}
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) {
a.requestedOrientation = currentOrientation
}
} else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
} else if (bypassRotationPrevention) {
a.requestedOrientation = currentOrientation
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
a.requestedOrientation = currentOrientation
} else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
} else if (bypassRotationPrevention) {
a.requestedOrientation = currentOrientation
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
a.requestedOrientation = currentOrientation
} else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
if (isFs && isMaximized) {
if (isFullScreenPortraitAllowed) {
a.requestedOrientation = currentOrientation
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
a.requestedOrientation = currentOrientation
} else if (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
//Don't change anything
} else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
} else if (bypassRotationPrevention) {
a.requestedOrientation = currentOrientation
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
a.requestedOrientation = currentOrientation
} else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}");
Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, fullAutorotateLock = ${fullAutorotateLock}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}");
}
override fun onShownWithView(parameter: Any?, isBack: Boolean) {

View file

@ -2360,20 +2360,11 @@ class VideoDetailView : ConstraintLayout {
_layoutRecommended.visibility = View.VISIBLE
_commentsList.clear()
val url = _url
if (url != null) {
_layoutRecommended.addView(LoaderView(context).apply {
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
start()
})
_taskLoadRecommendations.run(url)
} else {
_layoutRecommended.addView(TextView(context).apply {
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
textSize = 12.0f
text = "No recommendations found"
})
}
_layoutRecommended.addView(LoaderView(context).apply {
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
start()
})
_taskLoadRecommendations.run(null)
}
}
@ -2385,7 +2376,7 @@ class VideoDetailView : ConstraintLayout {
if (_tabIndex == 2) {
_layoutRecommended.removeAllViews()
if (results == null) {
if (results == null || results.isEmpty()) {
_layoutRecommended.addView(TextView(context).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
@ -2767,7 +2758,15 @@ class VideoDetailView : ConstraintLayout {
}
} 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") }
.exception<Throwable> {
setRecommendations(null, it.message)

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.SourcePluginConfig
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.models.ImageVariable
import com.futo.platformplayer.stores.FragmentedStorage
@ -128,7 +130,15 @@ class StatePlugins {
return false;
LoginActivity.showLogin(context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
try {
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) {
StatePlatform.instance.reloadClient(context, id);

View file

@ -29,6 +29,14 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="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
android:id="@+id/button_cast"
android:layout_width="50dp"
@ -133,20 +141,6 @@
android:scaleType="fitCenter"
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
android:id="@+id/text_position"
android:layout_width="wrap_content"

View file

@ -57,6 +57,14 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="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
android:id="@+id/button_cast"
android:layout_width="50dp"
@ -147,20 +155,6 @@
app:layout_constraintLeft_toRightOf="@id/layout_play_pause"
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
android:id="@+id/button_fullscreen"
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="rotation_zone">Rotation zone</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_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_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>
@ -966,4 +970,12 @@
<item>30</item>
<item>45</item>
</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>