mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-17 13:32:38 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a0c16524a | |||
| 9b843a155e | |||
| cb085acbff | |||
| c3d7df166b | |||
| d312062125 | |||
| e2453192aa | |||
| 0f4e4a7d97 | |||
| f20a708b36 | |||
| 8c4e511883 | |||
| a4a3b8d664 | |||
| bf6530ea81 | |||
| 4a80c2aab1 | |||
| 527bbfe43f | |||
| d8e1edb60b | |||
| 245b5f74c0 | |||
| e9a1f63415 | |||
| ec370dd94b | |||
| e39d862ef3 | |||
| 7b065654aa | |||
| 918b2bbe96 | |||
| e529a3d34d | |||
| 5475778d67 |
@@ -471,14 +471,30 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.full_screen_portrait, FieldForm.TOGGLE, R.string.allow_full_screen_portrait, 13)
|
||||
var fullscreenPortrait: Boolean = false;
|
||||
|
||||
@FormField(R.string.reverse_portrait, FieldForm.TOGGLE, R.string.reverse_portrait_description, 14)
|
||||
var reversePortrait: Boolean = false;
|
||||
|
||||
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 14)
|
||||
@FormField(R.string.rotation_zone, FieldForm.DROPDOWN, R.string.rotation_zone_description, 15)
|
||||
@DropdownFieldOptionsId(R.array.rotation_zone)
|
||||
var rotationZone: Int = 2;
|
||||
|
||||
@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, 15)
|
||||
@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, 16)
|
||||
@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, 21)
|
||||
var autoplay: Boolean = false;
|
||||
}
|
||||
|
||||
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
||||
@@ -494,6 +510,9 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.default_recommendations, FieldForm.TOGGLE, R.string.default_recommendations_description, 0)
|
||||
var recommendationsDefault: Boolean = false;
|
||||
|
||||
@FormField(R.string.hide_recommendations, FieldForm.TOGGLE, R.string.hide_recommendations_description, 0)
|
||||
var hideRecommendations: Boolean = false;
|
||||
|
||||
@FormField(R.string.bad_reputation_comments_fading, FieldForm.TOGGLE, R.string.bad_reputation_comments_fading_description, 0)
|
||||
var badReputationCommentsFading: Boolean = true;
|
||||
|
||||
@@ -545,7 +564,7 @@ class Settings : FragmentedStorageFileJson() {
|
||||
class Browsing {
|
||||
@FormField(R.string.enable_video_cache, FieldForm.TOGGLE, R.string.cache_to_quickly_load_previously_fetched_videos, 0)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var videoCache: Boolean = true;
|
||||
var videoCache: Boolean = false; //Temporary default disabled to prevent ui freeze?
|
||||
}
|
||||
|
||||
@FormField(R.string.casting, "group", R.string.configure_casting, 9)
|
||||
|
||||
@@ -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,24 +18,43 @@ 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
|
||||
2 -> 45
|
||||
else -> 45
|
||||
}
|
||||
|
||||
val newOrientation = when {
|
||||
orientation in 45..134 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
orientation in 135..224 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
orientation in 225..314 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
orientation in 315..360 || orientation in 0..44 -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
orientation in (90 - rotationZone)..(90 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
orientation in (180 - rotationZone)..(180 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
orientation in (270 - rotationZone)..(270 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
orientation in (360 - rotationZone)..(360 + rotationZone - 1) || orientation in 0..(rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
else -> lastOrientation
|
||||
}
|
||||
|
||||
if (newOrientation != lastStableOrientation) {
|
||||
lastStableOrientation = newOrientation
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
_currentJob?.cancel()
|
||||
_currentJob = lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
delay(stabilityThresholdTime)
|
||||
if (newOrientation == lastStableOrientation) {
|
||||
@@ -55,6 +75,8 @@ class SimpleOrientationListener(
|
||||
}
|
||||
|
||||
fun stopListening() {
|
||||
_currentJob?.cancel()
|
||||
_currentJob = null
|
||||
orientationListener.disable()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+19
-15
@@ -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<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
|
||||
|
||||
@@ -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<BrowserFragment>(Settings.URL_FAQ);
|
||||
})
|
||||
//96 is reserved for privacy button
|
||||
//98 is reserved for buy button
|
||||
|
||||
+27
-7
@@ -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) {
|
||||
|
||||
+40
-20
@@ -97,36 +97,56 @@ 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
|
||||
val currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
|
||||
var currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
|
||||
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT && !Settings.instance.playback.reversePortrait)
|
||||
currentOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
|
||||
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) {
|
||||
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
|
||||
}
|
||||
} else if (bypassRotationPrevention) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
} 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) {
|
||||
|
||||
+96
-57
@@ -290,6 +290,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
private var _commentsCount = 0;
|
||||
private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null;
|
||||
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
||||
private var _autoplayVideo: IPlatformVideo? = null
|
||||
|
||||
//Events
|
||||
val onMinimize = Event0();
|
||||
@@ -720,6 +721,17 @@ class VideoDetailView : ConstraintLayout {
|
||||
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
};
|
||||
|
||||
StatePlayer.instance.autoplayChanged.subscribe(this) {
|
||||
if (it) {
|
||||
val url = _url
|
||||
val autoPlayVideo = _autoplayVideo
|
||||
if (url != null && autoPlayVideo == null) {
|
||||
_taskLoadRecommendations.cancel()
|
||||
_taskLoadRecommendations.run(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_layoutResume.setOnClickListener {
|
||||
handleSeek(_historicalPosition * 1000);
|
||||
|
||||
@@ -1006,6 +1018,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_container_content_queue.cleanup();
|
||||
_container_content_description.cleanup();
|
||||
_container_content_support.cleanup();
|
||||
StatePlayer.instance.autoplayChanged.remove(this)
|
||||
StateCasting.instance.onActiveDevicePlayChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
||||
@@ -1102,6 +1115,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
this.video = null;
|
||||
cleanupPlaybackTracker();
|
||||
_searchVideo = video;
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (setVideoOverview)")
|
||||
_videoResumePositionMilliseconds = resumeSeconds * 1000;
|
||||
setLastPositionMilliseconds(_videoResumePositionMilliseconds, false);
|
||||
_addCommentView.setContext(null, null);
|
||||
@@ -1191,6 +1206,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
||||
_didTriggerDatasourceErrroCount = 0;
|
||||
_didTriggerDatasourceError = false;
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (setVideoDetails)")
|
||||
|
||||
if(newVideo && this.video?.url == videoDetail.url)
|
||||
return;
|
||||
@@ -1297,13 +1314,13 @@ class VideoDetailView : ConstraintLayout {
|
||||
if (video is TutorialFragment.TutorialVideo) {
|
||||
setTabIndex(0, true)
|
||||
} else {
|
||||
if (Settings.instance.comments.recommendationsDefault) {
|
||||
setTabIndex(2)
|
||||
if (Settings.instance.comments.recommendationsDefault && !Settings.instance.comments.hideRecommendations) {
|
||||
setTabIndex(2, true)
|
||||
} else {
|
||||
when(Settings.instance.comments.defaultCommentSection) {
|
||||
0 -> if(Settings.instance.other.polycentricEnabled) setTabIndex(0) else setTabIndex(1);
|
||||
1 -> setTabIndex(1);
|
||||
2 -> setTabIndex(StateMeta.instance.getLastCommentSection())
|
||||
0 -> if(Settings.instance.other.polycentricEnabled) setTabIndex(0, true) else setTabIndex(1, true);
|
||||
1 -> setTabIndex(1, true);
|
||||
2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1334,6 +1351,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
setDescription(video.description.fixHtmlLinks());
|
||||
_creatorThumbnail.setThumbnail(video.author.thumbnail, false);
|
||||
|
||||
|
||||
val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url, true);
|
||||
if (cachedPolycentricProfile != null) {
|
||||
setPolycentricProfile(cachedPolycentricProfile, animate = false);
|
||||
@@ -1493,6 +1511,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
if(video.isLive && video.live == null && !video.video.videoSources.any())
|
||||
startLiveTry(video);
|
||||
|
||||
|
||||
_player.updateNextPrevious();
|
||||
updateMoreButtons();
|
||||
|
||||
@@ -1509,6 +1528,11 @@ class VideoDetailView : ConstraintLayout {
|
||||
_layoutRating.visibility = View.VISIBLE
|
||||
_layoutChangeBottomSection.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (StatePlayer.instance.autoplay) {
|
||||
_taskLoadRecommendations.cancel()
|
||||
_taskLoadRecommendations.run(videoDetail.url)
|
||||
}
|
||||
}
|
||||
fun loadLiveChat(video: IPlatformVideoDetails) {
|
||||
_liveChat?.stop();
|
||||
@@ -1618,7 +1642,6 @@ class VideoDetailView : ConstraintLayout {
|
||||
});
|
||||
else
|
||||
_player.setArtwork(null);
|
||||
|
||||
_player.setSource(videoSource, audioSource, _playWhenReady, false);
|
||||
if(subtitleSource != null)
|
||||
_player.swapSubtitles(fragment.lifecycleScope, subtitleSource);
|
||||
@@ -1778,6 +1801,14 @@ class VideoDetailView : ConstraintLayout {
|
||||
fun nextVideo(forceLoop: Boolean = false, withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false): Boolean {
|
||||
Logger.i(TAG, "nextVideo")
|
||||
var next = StatePlayer.instance.nextQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9, bypassVideoLoop);
|
||||
val autoplayVideo = _autoplayVideo
|
||||
if (next == null && autoplayVideo != null && StatePlayer.instance.autoplay) {
|
||||
Logger.i(TAG, "Found autoplay video!")
|
||||
StatePlayer.instance.setAutoplayed(autoplayVideo.url)
|
||||
next = autoplayVideo
|
||||
}
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (nextVideo)")
|
||||
if(next == null && forceLoop)
|
||||
next = StatePlayer.instance.restartQueue();
|
||||
if(next != null) {
|
||||
@@ -2302,6 +2333,9 @@ class VideoDetailView : ConstraintLayout {
|
||||
return
|
||||
}
|
||||
|
||||
val recommendationsHidden = Settings.instance.comments.hideRecommendations
|
||||
_buttonRecommended.visibility = if (recommendationsHidden) View.GONE else View.VISIBLE
|
||||
|
||||
_taskLoadRecommendations.cancel()
|
||||
_tabIndex = index
|
||||
_buttonRecommended.setTextColor(resources.getColor(if (index == 2) R.color.white else R.color.gray_ac))
|
||||
@@ -2318,7 +2352,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_layoutRecommended.visibility = View.GONE
|
||||
fetchPolycentricComments()
|
||||
} else if (index == 1) {
|
||||
_addCommentView.visibility = View.VISIBLE
|
||||
_addCommentView.visibility = View.GONE
|
||||
_layoutRecommended.visibility = View.GONE
|
||||
fetchComments()
|
||||
} else if (index == 2) {
|
||||
@@ -2326,60 +2360,57 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRecommendations(pager: IPager<IPlatformContent>?, message: String? = null) {
|
||||
_layoutRecommended.removeAllViews()
|
||||
if (pager == null) {
|
||||
_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))
|
||||
}
|
||||
textAlignment = TEXT_ALIGNMENT_CENTER
|
||||
textSize = 14.0f
|
||||
text = message
|
||||
})
|
||||
return
|
||||
private fun setRecommendations(results: List<IPlatformVideo>?, message: String? = null) {
|
||||
if (results != null && StatePlayer.instance.autoplay) {
|
||||
_autoplayVideo = results.firstOrNull { !StatePlayer.instance.wasAutoplayed(it.url) }
|
||||
Logger.i(TAG, "Autoplay video set (url = ${_autoplayVideo?.url})")
|
||||
}
|
||||
|
||||
val results = pager.getResults().filter { it is IPlatformVideo }
|
||||
for (result in results) {
|
||||
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||
bind(result)
|
||||
|
||||
hideAddTo()
|
||||
|
||||
onVideoClicked.subscribe { video, _ ->
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
onChannelClicked.subscribe {
|
||||
fragment.navigate<ChannelFragment>(it)
|
||||
}
|
||||
|
||||
onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
if (_tabIndex == 2) {
|
||||
_layoutRecommended.removeAllViews()
|
||||
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))
|
||||
}
|
||||
}
|
||||
})
|
||||
textAlignment = TEXT_ALIGNMENT_CENTER
|
||||
textSize = 14.0f
|
||||
text = message
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for (result in results) {
|
||||
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||
bind(result)
|
||||
|
||||
hideAddTo()
|
||||
|
||||
onVideoClicked.subscribe { video, _ ->
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
onChannelClicked.subscribe {
|
||||
fragment.navigate<ChannelFragment>(it)
|
||||
}
|
||||
|
||||
onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2727,8 +2758,16 @@ 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)) })
|
||||
.success { setRecommendations(it, "No recommendations found") }
|
||||
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)
|
||||
Logger.w(TAG, "Failed to load recommendations.", it);
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
@@ -45,6 +46,33 @@ class StatePlayer {
|
||||
onRotationLockChanged.emit(value)
|
||||
}
|
||||
val onRotationLockChanged = Event1<Boolean>()
|
||||
var autoplay: Boolean = Settings.instance.playback.autoplay
|
||||
get() = field
|
||||
set(value) {
|
||||
if (field != value)
|
||||
_autoplayed.clear()
|
||||
field = value
|
||||
autoplayChanged.emit(value)
|
||||
}
|
||||
private val _autoplayed = hashSetOf<String>()
|
||||
fun wasAutoplayed(url: String?): Boolean {
|
||||
if (url == null) {
|
||||
return false
|
||||
}
|
||||
synchronized(_autoplayed) {
|
||||
return _autoplayed.contains(url)
|
||||
}
|
||||
}
|
||||
fun setAutoplayed(url: String?) {
|
||||
if (url == null) {
|
||||
return
|
||||
}
|
||||
synchronized(_autoplayed) {
|
||||
_autoplayed.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
val autoplayChanged = Event1<Boolean>()
|
||||
var loopVideo : Boolean = false;
|
||||
|
||||
val isPlaying: Boolean get() = _exoplayer?.player?.playWhenReady ?: false;
|
||||
@@ -138,6 +166,12 @@ class StatePlayer {
|
||||
}
|
||||
}
|
||||
|
||||
fun isUrlInQueue(url : String) : Boolean {
|
||||
synchronized(_queue) {
|
||||
return _queue.any { it.url == url };
|
||||
}
|
||||
}
|
||||
|
||||
fun getQueueType() : String {
|
||||
return _queueType;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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);
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setMargins
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
@@ -74,6 +75,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
|
||||
//Custom buttons
|
||||
private val _control_fullscreen: ImageButton;
|
||||
private val _control_autoplay: ImageButton;
|
||||
private val _control_videosettings: ImageButton;
|
||||
private val _control_minimize: ImageButton;
|
||||
private val _control_rotate_lock: ImageButton;
|
||||
@@ -92,6 +94,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
private val _control_videosettings_fullscreen: ImageButton;
|
||||
private val _control_minimize_fullscreen: ImageButton;
|
||||
private val _control_rotate_lock_fullscreen: ImageButton;
|
||||
private val _control_autoplay_fullscreen: ImageButton;
|
||||
private val _control_loop_fullscreen: ImageButton;
|
||||
private val _control_cast_fullscreen: ImageButton;
|
||||
private val _control_play_fullscreen: ImageButton;
|
||||
@@ -149,6 +152,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
|
||||
videoControls = findViewById(R.id.video_player_controller);
|
||||
_control_fullscreen = videoControls.findViewById(R.id.button_fullscreen);
|
||||
_control_autoplay = videoControls.findViewById(R.id.button_autoplay);
|
||||
_control_videosettings = videoControls.findViewById(R.id.button_settings);
|
||||
_control_minimize = videoControls.findViewById(R.id.button_minimize);
|
||||
_control_rotate_lock = videoControls.findViewById(R.id.button_rotate_lock);
|
||||
@@ -164,6 +168,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
_control_duration = videoControls.findViewById(R.id.text_duration);
|
||||
|
||||
_videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen);
|
||||
_control_autoplay_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_autoplay);
|
||||
_control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_fullscreen);
|
||||
_control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_minimize);
|
||||
_control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_settings);
|
||||
@@ -386,6 +391,18 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
UIDialogs.showCastingDialog(context);
|
||||
};
|
||||
|
||||
_control_autoplay.setOnClickListener {
|
||||
StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay;
|
||||
updateAutoplayButton()
|
||||
}
|
||||
updateAutoplayButton()
|
||||
|
||||
_control_autoplay_fullscreen.setOnClickListener {
|
||||
StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay;
|
||||
updateAutoplayButton()
|
||||
}
|
||||
updateAutoplayButton()
|
||||
|
||||
val progressUpdateListener = { position: Long, bufferedPosition: Long ->
|
||||
val currentTime = position.formatDuration()
|
||||
val currentDuration = duration.formatDuration()
|
||||
@@ -433,6 +450,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAutoplayButton() {
|
||||
_control_autoplay.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
|
||||
_control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
|
||||
}
|
||||
|
||||
private fun setSystemBrightness(brightness: Float) {
|
||||
Log.i(TAG, "setSystemBrightness $brightness")
|
||||
if (android.provider.Settings.System.canWrite(context)) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M400,623.08L400,336.92L623.08,480L400,623.08ZM480,880Q363.54,880 269.42,820.12Q175.31,760.23 120,651.23L120,800L80,800L80,584.62L294.62,584.62L294.62,624.62L152,624.62Q198.38,724.23 285.73,782.12Q373.08,840 480,840Q598.85,840 693.5,769Q788.15,698 823.08,584.38L862.62,592.38Q825.31,721.46 719.54,800.73Q613.77,880 480,880ZM82,440Q89.77,377.62 110.15,328.04Q130.54,278.46 169.92,227.23L199.23,255Q167.23,296.77 149.54,339.04Q131.85,381.31 122.23,440L82,440ZM255.23,199.77L227.46,170.46Q275.85,132.62 329.54,110.58Q383.23,88.54 440,83.54L440,123.54Q391.31,128.54 344.15,148.15Q297,167.77 255.23,199.77ZM703.46,199.77Q667.08,169.31 616.73,148.54Q566.38,127.77 520,123.54L520,83.54Q577,88.77 630.81,111.08Q684.62,133.38 732,171.23L703.46,199.77ZM836.46,440Q829.92,384.38 810.31,338.65Q790.69,292.92 758.69,255.77L787.23,227.23Q825.85,273.08 848.15,326.88Q870.46,380.69 876.46,440L836.46,440Z"/>
|
||||
</vector>
|
||||
@@ -112,7 +112,8 @@
|
||||
app:scrubber_drawable="@drawable/player_thumb"
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_enabled_size="16dp"
|
||||
app:scrubber_dragged_size="20dp"
|
||||
app:played_color="@color/colorPrimary"
|
||||
app:buffered_color="#DDEEEEEE"
|
||||
app:unplayed_color="#55EEEEEE" />
|
||||
|
||||
@@ -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"
|
||||
@@ -205,7 +213,8 @@
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_drawable="@drawable/player_thumb"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_enabled_size="16dp"
|
||||
app:scrubber_dragged_size="20dp"
|
||||
app:played_color="@color/transparent"
|
||||
app:buffered_color="@color/transparent"
|
||||
app:unplayed_color="@color/transparent"
|
||||
|
||||
@@ -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"
|
||||
@@ -237,7 +245,8 @@
|
||||
app:scrubber_drawable="@drawable/player_thumb"
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_enabled_size="16dp"
|
||||
app:scrubber_dragged_size="20dp"
|
||||
app:played_color="@color/colorPrimary"
|
||||
app:buffered_color="#AAEEEEEE"
|
||||
app:unplayed_color="#88EEEEEE"
|
||||
|
||||
@@ -178,7 +178,8 @@
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_drawable="@drawable/player_thumb"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_enabled_size="16dp"
|
||||
app:scrubber_dragged_size="20dp"
|
||||
app:played_color="@color/colorPrimary"
|
||||
app:buffered_color="@color/transparent"
|
||||
app:unplayed_color="#7F7F7F"
|
||||
|
||||
@@ -373,12 +373,22 @@
|
||||
<string name="system_volume_descr">Gesture controls adjust system volume</string>
|
||||
<string name="live_chat_webview">Live Chat Webview</string>
|
||||
<string name="full_screen_portrait">Fullscreen portrait</string>
|
||||
<string name="reverse_portrait">Allow 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_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>
|
||||
<string name="allow_under_cutout_description">Allow video to go underneath the screen cutout in full-screen.\nMay require restart</string>
|
||||
<string name="autoplay">Enable autoplay by default</string>
|
||||
<string name="autoplay_description">Autoplay will be enabled by default whenever you watch a video</string>
|
||||
<string name="allow_full_screen_portrait">Allow fullscreen portrait</string>
|
||||
<string name="background_switch_audio">Switch to Audio in Background</string>
|
||||
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
|
||||
@@ -412,6 +422,8 @@
|
||||
<string name="preferred_preview_quality_description">Default quality while previewing a video in a feed</string>
|
||||
<string name="primary_language">Primary Language</string>
|
||||
<string name="default_comment_section">Default Comment Section</string>
|
||||
<string name="hide_recommendations">Hide Recommendations</string>
|
||||
<string name="hide_recommendations_description">Fully hide the recommendations tab.</string>
|
||||
<string name="default_recommendations">Recommendations as Default</string>
|
||||
<string name="default_recommendations_description">Show recommendations as default, instead of comments.</string>
|
||||
<string name="bad_reputation_comments_fading">Bad Reputation Comment Fading</string>
|
||||
@@ -953,4 +965,17 @@
|
||||
<item>Within 30 seconds of loss</item>
|
||||
<item>Always</item>
|
||||
</string-array>
|
||||
<string-array name="rotation_zone">
|
||||
<item>15</item>
|
||||
<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>
|
||||
Submodule app/src/stable/assets/sources/youtube updated: c0a601817d...35b56d380a
Submodule app/src/unstable/assets/sources/youtube updated: c0a601817d...35b56d380a
Reference in New Issue
Block a user