From 527bbfe43f26b00814f21c8a024f1394636b0f3c Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 9 Sep 2024 23:07:15 +0200 Subject: [PATCH 01/11] Fix watchlater re-downloading every time videos are reordered --- .../futo/platformplayer/states/StatePlaylists.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt index 41de7c3c..d5f53af4 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -61,8 +61,17 @@ class StatePlaylists { } fun updateWatchLater(updated: List) { synchronized(_watchlistStore) { - _watchlistStore.deleteAll(); - _watchlistStore.saveAllAsync(updated); + //_watchlistStore.deleteAll(); + 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.save(); } From 4a80c2aab10fdd5a3ed4a6932ccb842c783750ad Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Sep 2024 10:34:36 +0200 Subject: [PATCH 02/11] Moved Autoplay button to top and load recommendations now appropriately uses 'StatePlatform.instance.getContentRecommendations(v.url)' for local videos. --- .../mainactivity/main/VideoDetailView.kt | 29 +++++++++---------- app/src/main/res/layout/video_player_ui.xml | 22 +++++--------- .../res/layout/video_player_ui_fullscreen.xml | 22 +++++--------- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 6d44996f..b001360c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -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) } } @@ -2767,7 +2758,15 @@ class VideoDetailView : ConstraintLayout { } } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); - private val _taskLoadRecommendations = TaskHandler?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) }) + private val _taskLoadRecommendations = TaskHandler?>(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 { setRecommendations(null, it.message) diff --git a/app/src/main/res/layout/video_player_ui.xml b/app/src/main/res/layout/video_player_ui.xml index 32a9f5cf..e849b942 100644 --- a/app/src/main/res/layout/video_player_ui.xml +++ b/app/src/main/res/layout/video_player_ui.xml @@ -29,6 +29,14 @@ android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent"> + - - + - - Date: Tue, 10 Sep 2024 11:59:44 +0200 Subject: [PATCH 03/11] Implement full autorotate lock (default off). --- .../java/com/futo/platformplayer/Settings.kt | 11 +++-- .../mainactivity/main/VideoDetailFragment.kt | 49 +++++++++++++------ app/src/main/res/values/strings.xml | 2 + 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index ffb6e3a5..67fa246c 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -478,15 +478,18 @@ 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.full_autorotate_lock, FieldForm.TOGGLE, R.string.full_autorotate_lock_description, 16) + var fullAutorotateLock: Boolean = false; + + @FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 17) 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, 18) 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, 19) var allowVideoToGoUnderCutout: Boolean = true; - @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 19) + @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 20) var autoplay: Boolean = false; } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index b1a498f5..63b2f556 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -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,45 @@ 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 { + 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) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 064f0bdf..ae3e1162 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -379,6 +379,8 @@ Specify the sensitivity of rotation zones (decrease to make less sensitive) Prefer Webm Video Codecs If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility. + Full auto rotate lock + Prevent any rotation while rotation lock is engaged (even flipping between landscape and landscape reverse). Prefer Webm Audio Codecs If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility. Allow video under cutout From 8c4e511883dc459b258264e51918395705b88e89 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Sep 2024 12:24:42 +0200 Subject: [PATCH 04/11] Allow more tabs to be hidden. --- .../bottombar/MenuBottomBarFragment.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index d3003991..fa99c302 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -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() })) } - newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, { - it.navigate(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() }), ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate() }), - ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate() }), + ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = true, { it.currentMain is SourcesFragment }, { it.navigate() }), ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate() }), ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate() }), ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate() }), ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate() }), ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate() }), ButtonDefinition(10, R.drawable.ic_help_square, R.drawable.ic_help_square_fill, R.string.tutorials, canToggle = true, { it.currentMain is TutorialFragment }, { it.navigate() }), + ButtonDefinition(11, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, { + it.navigate(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 ); From f20a708b36b4d02626b4d7caebc9fd9225d1406c Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Sep 2024 12:25:30 +0200 Subject: [PATCH 05/11] Check both length and null for 'No recommendations found' --- .../fragment/mainactivity/main/VideoDetailView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index b001360c..ee835a3e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2376,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)) From 0f4e4a7d9739302f1041f78f683018b2a66e48fc Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Sep 2024 15:54:27 +0200 Subject: [PATCH 06/11] Allow configuring stability threshold time and ensure there is no more than 1 job active at a time for SimpleOrientationListener. --- .../java/com/futo/platformplayer/Settings.kt | 14 +++++++++----- .../SimpleOrientationListener.kt | 18 ++++++++++++++++-- app/src/main/res/values/strings.xml | 10 ++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 67fa246c..784c2c36 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -478,18 +478,22 @@ class Settings : FragmentedStorageFileJson() { @DropdownFieldOptionsId(R.array.rotation_zone) var rotationZone: Int = 2; - @FormField(R.string.full_autorotate_lock, FieldForm.TOGGLE, R.string.full_autorotate_lock_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, 17) + @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, 18) + @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, 19) + @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, 20) + @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 21) var autoplay: Boolean = false; } diff --git a/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt b/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt index a289f02b..9fc4367b 100644 --- a/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt +++ b/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt @@ -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() 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() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae3e1162..5097d091 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -377,6 +377,8 @@ Allow app to flip into reverse portrait Rotation zone Specify the sensitivity of rotation zones (decrease to make less sensitive) + Stability threshold time + Specify the duration the orientation needs to be the same to trigger a rotation Prefer Webm Video Codecs If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility. Full auto rotate lock @@ -968,4 +970,12 @@ 30 45 + + 100 + 500 + 750 + 1000 + 1500 + 2000 + \ No newline at end of file From e2453192aa716afafe685ce2d4025b77cc63f138 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Sep 2024 17:27:00 +0200 Subject: [PATCH 07/11] More gracefully handle failing to set plugin auth. --- .../mainactivity/main/SourceDetailFragment.kt | 34 +++++++++++++++---- .../mainactivity/main/VideoDetailFragment.kt | 2 ++ .../platformplayer/states/StatePlugins.kt | 12 ++++++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index 7f4ea091..c5f3d430 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -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) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index 63b2f556..1530537b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -132,6 +132,8 @@ class VideoDetailFragment : MainFragment { 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 } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 420a3721..71aa6d6f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -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); From d3120621256fd29aeb78ac37f87858cb6a47f91d Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 10 Sep 2024 20:40:14 +0200 Subject: [PATCH 08/11] Fix content recommendations on offline videos --- .../com/futo/platformplayer/api/media/platforms/js/JSClient.kt | 3 ++- app/src/unstable/assets/sources/youtube | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 8a6b40b0..2b6deaf8 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -237,7 +237,8 @@ open class JSClient : IPlatformClient { hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false, hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: 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 { diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 4cd358c2..04e1c069 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758 +Subproject commit 04e1c0699354b928ea89c628a1cc23c0ca0fd88d From cb085acbff195cf73721c397ffcf7f55f6b20a10 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 10 Sep 2024 21:06:30 +0200 Subject: [PATCH 09/11] Submods --- app/src/stable/assets/sources/youtube | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 4cd358c2..04e1c069 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758 +Subproject commit 04e1c0699354b928ea89c628a1cc23c0ca0fd88d From 9b843a155e9b0a5c877825c699d7119e69895020 Mon Sep 17 00:00:00 2001 From: Kelvin <41-kelvin@users.noreply.gitlab.futo.org> Date: Tue, 10 Sep 2024 20:51:30 +0000 Subject: [PATCH 10/11] Revert "Allow more tabs to be hidden." This reverts commit 8c4e511883dc459b258264e51918395705b88e89 --- .../bottombar/MenuBottomBarFragment.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index fa99c302..d3003991 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -310,6 +310,19 @@ 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() })) } + newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, { + it.navigate(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 @@ -366,26 +379,13 @@ 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() }), ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate() }), - ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = true, { it.currentMain is SourcesFragment }, { it.navigate() }), + ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate() }), ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate() }), ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate() }), ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate() }), ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate() }), ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate() }), ButtonDefinition(10, R.drawable.ic_help_square, R.drawable.ic_help_square_fill, R.string.tutorials, canToggle = true, { it.currentMain is TutorialFragment }, { it.navigate() }), - ButtonDefinition(11, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, { - it.navigate(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,6 +396,7 @@ 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 ); From 0a0c16524aa73715a67d8ef4510536e1367fd52b Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 10 Sep 2024 23:10:36 +0200 Subject: [PATCH 11/11] Allow hiding privacy mode and FAQ without breaking existing orderings --- .../bottombar/MenuBottomBarFragment.kt | 34 +++++++++++-------- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index d3003991..06beee6a 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -209,26 +209,30 @@ class MenuBottomBarFragment : MainActivityFragment() { _moreButtons.clear(); _layoutMoreButtons.removeAllViews(); + var insertedButtons = 0; //Force buy to be on top for more buttons val buyIndex = buttons.indexOfFirst { b -> b.id == 98 }; if (buyIndex != -1) { val button = buttons[buyIndex] buttons.removeAt(buyIndex) buttons.add(0, button) + insertedButtons++; } //Force faq to be second val faqIndex = buttons.indexOfFirst { b -> b.id == 97 }; if (faqIndex != -1) { val button = buttons[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 val privacyIndex = buttons.indexOfFirst { b -> b.id == 96 }; if (privacyIndex != -1) { val button = buttons[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) { @@ -310,19 +314,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() })) } - newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, { - it.navigate(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 @@ -395,6 +386,19 @@ class MenuBottomBarFragment : MainActivityFragment() { if (c is Activity) { 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(Settings.URL_FAQ); }) //96 is reserved for privacy button //98 is reserved for buy button diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 04e1c069..35b56d38 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 04e1c0699354b928ea89c628a1cc23c0ca0fd88d +Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 04e1c069..35b56d38 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 04e1c0699354b928ea89c628a1cc23c0ca0fd88d +Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d