mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-25 11:48:38 +00:00
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:
commit
74efec3235
13 changed files with 131 additions and 80 deletions
|
@ -470,15 +470,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -216,26 +216,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) {
|
||||
|
@ -327,19 +331,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
|
||||
|
||||
|
@ -412,6 +403,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<BrowserFragment>(Settings.URL_FAQ);
|
||||
})
|
||||
//96 is reserved for privacy button
|
||||
//98 is reserved for buy button
|
||||
|
|
|
@ -397,23 +397,43 @@ class SourceDetailFragment : MainFragment() {
|
|||
UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Login", {
|
||||
LoginActivity.showLogin(StateApp.instance.context, config) {
|
||||
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) {
|
||||
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;
|
||||
|
||||
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) {
|
||||
|
|
|
@ -116,17 +116,18 @@ class VideoDetailFragment : MainFragment {
|
|||
fun updateOrientation() {
|
||||
val a = activity ?: return
|
||||
// only applies to small windows
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait;
|
||||
val isReversePortraitAllowed = Settings.instance.playback.reversePortrait;
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait
|
||||
val isReversePortraitAllowed = Settings.instance.playback.reversePortrait
|
||||
val fullAutorotateLock = Settings.instance.playback.fullAutorotateLock
|
||||
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
|
||||
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
|
||||
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) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
} else {
|
||||
|
|
|
@ -2364,20 +2364,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"
|
||||
})
|
||||
}
|
||||
_taskLoadRecommendations.run(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2389,7 +2380,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))
|
||||
|
@ -2787,7 +2778,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)
|
||||
|
|
|
@ -61,8 +61,17 @@ class StatePlaylists {
|
|||
}
|
||||
fun updateWatchLater(updated: List<SerializedPlatformVideo>) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -1 +1 @@
|
|||
Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758
|
||||
Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d
|
|
@ -1 +1 @@
|
|||
Subproject commit 4cd358c2bd11e016bdfe1ed341fd5c7e49219758
|
||||
Subproject commit 35b56d380a9ae6ef85ba8ec16cecb0a86d4efa1d
|
Loading…
Add table
Add a link
Reference in a new issue