diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9ef606c7..6c69fb32 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,7 +51,6 @@
android:name=".activities.MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
- android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar"
android:launchMode="singleTask"
android:resizeableActivity="true"
@@ -146,11 +145,9 @@
-
-
@@ -217,7 +213,6 @@
android:name=".activities.ManageTabsActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" />
-
()
-
- 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 (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
-
- _currentJob?.cancel()
- _currentJob = lifecycleScope.launch(Dispatchers.Main) {
- try {
- delay(stabilityThresholdTime)
- if (newOrientation == lastStableOrientation) {
- lastOrientation = newOrientation
- onOrientationChanged.emit(newOrientation)
- }
- } catch (e: Throwable) {
- Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
- }
- }
- }
- }
- }
-
- init {
- orientationListener.enable()
- lastOrientation = activity.resources.configuration.orientation
- }
-
- fun stopListening() {
- _currentJob?.cancel()
- _currentJob = null
- orientationListener.disable()
- }
-
- companion object {
- private val TAG = "SimpleOrientationListener"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt
index 76b06525..492b7c39 100644
--- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt
+++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt
@@ -28,6 +28,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.media3.common.util.UnstableApi
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
@@ -250,6 +251,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
}
+ @UnstableApi
override fun onCreate(savedInstanceState: Bundle?) {
Logger.i(TAG, "MainActivity Starting");
StateApp.instance.setGlobalContext(this, lifecycleScope);
@@ -513,6 +515,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
//startActivity(Intent(this, TestActivity::class.java));
+ // updates the requestedOrientation based on user settings
+ _fragVideoDetail.updateOrientation()
+
val sharedPreferences =
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
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 f0315665..c4afcf74 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
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -34,7 +35,7 @@ import kotlin.math.roundToInt
class MenuBottomBarFragment : MainActivityFragment() {
private var _view: MenuBottomBarView? = null;
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = MenuBottomBarView(this, inflater);
_view = view;
return view;
@@ -56,7 +57,13 @@ class MenuBottomBarFragment : MainActivityFragment() {
return _view?.onBackPressed() ?: false;
}
- @SuppressLint("ViewConstructor")
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+
+ _view?.updateAllButtonVisibility()
+ }
+
+ @SuppressLint("ViewConstructor")
class MenuBottomBarView : LinearLayout {
private val _fragment: MenuBottomBarFragment;
private val _inflater: LayoutInflater;
@@ -76,7 +83,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
private var _buttonsVisible = 0;
private var _subscriptionsVisible = true;
- var currentButtonDefinitions: List? = null;
+ private var currentButtonDefinitions: List? = null;
constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) {
_fragment = fragment;
@@ -132,7 +139,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
val staggerFactor = 3.0f
if (visible) {
- moreOverlay.visibility = LinearLayout.VISIBLE
+ moreOverlay.visibility = VISIBLE
val animations = arrayListOf()
animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration))
@@ -161,7 +168,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
animatorSet.doOnEnd {
_moreVisibleAnimating = false
_moreVisible = false
- moreOverlay.visibility = LinearLayout.INVISIBLE
+ moreOverlay.visibility = INVISIBLE
}
animatorSet.playTogether(animations)
animatorSet.start()
@@ -178,7 +185,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
_layoutBottomBarButtons.removeAllViews();
_layoutBottomBarButtons.addView(Space(context).apply {
- layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
+ layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
})
for ((index, button) in buttons.withIndex()) {
@@ -192,7 +199,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
_layoutBottomBarButtons.addView(menuButton)
if (index < buttonDefinitions.size - 1) {
_layoutBottomBarButtons.addView(Space(context).apply {
- layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
+ layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
})
}
@@ -200,7 +207,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
}
_layoutBottomBarButtons.addView(Space(context).apply {
- layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
+ layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
})
}
@@ -255,9 +262,20 @@ class MenuBottomBarFragment : MainActivityFragment() {
button.updateActive(_fragment);
}
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+
+ updateAllButtonVisibility()
+ }
+
fun updateAllButtonVisibility() {
+ // if the more fly-out menu is open the we should close it
+ if(_moreVisible) {
+ setMoreVisible(false)
+ }
+
val defs = currentButtonDefinitions?.toMutableList() ?: return
- val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics;
+ val metrics = resources.displayMetrics
_buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt();
if (_buttonsVisible >= defs.size) {
updateBottomMenuButtons(defs.toMutableList(), false);
diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt
index 908730fe..6dccdde8 100644
--- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt
+++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt
@@ -4,7 +4,7 @@ import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.widget.LinearLayout
-import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
@@ -45,9 +45,7 @@ abstract class ContentFeedView : FeedView, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) {
-
- }
+ constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData)
override fun filterResults(results: List): List {
return results;
@@ -55,16 +53,10 @@ abstract class ContentFeedView : FeedView): InsertedViewAdapterWithLoader {
val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context);
- player.modifyState("ThumbnailPlayer", { state -> state.muted = true });
+ player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
_exoPlayer = player;
- val v = LinearLayout(context).apply {
- layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- orientation = LinearLayout.VERTICAL;
- };
- headerView = v;
-
- return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(v), arrayListOf(), shouldShowTimeBar).apply {
+ return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
attachAdapterEvents(this);
}
}
@@ -142,7 +134,10 @@ abstract class ContentFeedView : FeedView()
.filter { it != content };
- StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false);
+ StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue",
+ focus = true,
+ shuffle = false
+ );
})
);
}
@@ -160,21 +155,22 @@ abstract class ContentFeedView : FeedView, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) {
+ override fun onRestoreCachedData(cachedData: RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) {
super.onRestoreCachedData(cachedData)
- val v = LinearLayout(context).apply {
- layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- orientation = LinearLayout.VERTICAL;
- };
- headerView = v;
- cachedData.adapter.viewsToPrepend.add(v);
+
(cachedData.adapter as PreviewContentListAdapter?)?.let { attachAdapterEvents(it) };
}
- override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager {
- val llmResults = LinearLayoutManager(context);
- llmResults.orientation = LinearLayoutManager.VERTICAL;
- return llmResults;
+ override fun createLayoutManager(
+ recyclerResults: RecyclerView,
+ context: Context
+ ): GridLayoutManager {
+ val glmResults =
+ GridLayoutManager(
+ context,
+ (resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
+ );
+ return glmResults
}
override fun onScrollStateChanged(newState: Int) {
@@ -217,11 +213,11 @@ abstract class ContentFeedView : FeedView 1)
return;
- val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition();
- val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition();
+ val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition()
+ val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition()
val itemsVisible = lastVisible - firstVisible + 1;
val autoPlayIndex = (firstVisible + floor(itemsVisible / 2.0 + 0.49).toInt()).coerceAtLeast(0).coerceAtMost((recyclerData.results.size - 1));
@@ -241,7 +237,7 @@ abstract class ContentFeedView : FeedView : FeedView : FeedView, CreatorViewHolder> where TFragment : MainFragment {
override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator;
- constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater) {
-
- }
+ constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater)
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader {
return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(),
@@ -34,18 +28,31 @@ abstract class CreatorFeedView : FeedView : L
protected val _recyclerResults: RecyclerView;
protected val _overlayContainer: FrameLayout;
protected val _swipeRefresh: SwipeRefreshLayout;
- private val _progress_bar: ProgressBar;
+ private val _progressBar: ProgressBar;
private val _spinnerSortBy: Spinner;
private val _containerSortBy: LinearLayout;
private val _tagsView: TagsView;
@@ -44,7 +45,7 @@ abstract class FeedView : L
private var _loading: Boolean = true;
- private val _pager_lock = Object();
+ private val _pagerLock = Object();
private var _cache: ItemCache? = null;
open val visibleThreshold = 15;
@@ -58,21 +59,21 @@ abstract class FeedView : L
private var _activeTags: List? = null;
private var _nextPageHandler: TaskHandler>;
- val recyclerData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>;
+ val recyclerData: RecyclerData, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>;
val fragment: TFragment;
private val _scrollListener: RecyclerView.OnScrollListener;
private var _automaticNextPageCounter = 0;
- constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>? = null) : super(inflater.context) {
+ constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>? = null) : super(inflater.context) {
this.fragment = fragment;
inflater.inflate(R.layout.fragment_feed, this);
_textCentered = findViewById(R.id.text_centered);
_emptyPagerContainer = findViewById(R.id.empty_pager_container);
- _progress_bar = findViewById(R.id.progress_bar);
- _progress_bar.inactiveColor = Color.TRANSPARENT;
+ _progressBar = findViewById(R.id.progress_bar);
+ _progressBar.inactiveColor = Color.TRANSPARENT;
_swipeRefresh = findViewById(R.id.swipe_refresh);
val recyclerResults: RecyclerView = findViewById(R.id.list_results);
@@ -158,7 +159,7 @@ abstract class FeedView : L
super.onScrolled(recyclerView, dx, dy);
val visibleItemCount = _recyclerResults.childCount;
- val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition();
+ val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition()
//Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount")
if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) {
@@ -179,14 +180,13 @@ abstract class FeedView : L
if (firstVisibleItemPosition != RecyclerView.NO_POSITION) {
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
val itemHeight = firstVisibleView?.height ?: 0
- val occupiedSpace = recyclerData.results.size * itemHeight
+ val occupiedSpace = recyclerData.results.size / recyclerData.layoutManager.spanCount * itemHeight
val recyclerViewHeight = _recyclerResults.height
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight")
occupiedSpace >= recyclerViewHeight
} else {
false
}
-
}
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
if (!canScroll || filteredResults.isEmpty()) {
@@ -226,7 +226,19 @@ abstract class FeedView : L
}
}
+ open fun updateSpanCount() {
+ recyclerData.layoutManager.spanCount = (resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+
+ updateSpanCount()
+ }
+
fun onResume() {
+ updateSpanCount()
+
//Reload the pager if the plugin was killed
val pager = recyclerData.pager;
if((pager is MultiPager<*> && pager.findPager { it is JSPager<*> && !it.isAvailable } != null) ||
@@ -252,7 +264,7 @@ abstract class FeedView : L
protected open fun setActiveTags(activeTags: List?) {
_activeTags = activeTags;
- if (activeTags != null && activeTags.isNotEmpty()) {
+ if (!activeTags.isNullOrEmpty()) {
_tagsView.setTags(activeTags);
_tagsView.visibility = View.VISIBLE;
} else {
@@ -262,7 +274,7 @@ abstract class FeedView : L
protected open fun setSortByOptions(options: List?) {
_sortByOptions = options;
- if (options != null && options.isNotEmpty()) {
+ if (!options.isNullOrEmpty()) {
val allOptions = arrayListOf();
allOptions.add("Default");
allOptions.addAll(options);
@@ -277,19 +289,19 @@ abstract class FeedView : L
}
}
protected abstract fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader;
- protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager;
- protected open fun onRestoreCachedData(cachedData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>) {}
+ protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): GridLayoutManager;
+ protected open fun onRestoreCachedData(cachedData: RecyclerData, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>) {}
protected fun setProgress(fin: Int, total: Int) {
val progress = (fin.toFloat() / total);
- _progress_bar.progress = progress;
+ _progressBar.progress = progress;
if(progress > 0 && progress < 1)
{
- if(_progress_bar.height == 0)
- _progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
+ if(_progressBar.height == 0)
+ _progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
}
- else if(_progress_bar.height > 0) {
- _progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
+ else if(_progressBar.height > 0) {
+ _progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
}
}
@@ -345,7 +357,7 @@ abstract class FeedView : L
//insertPagerResults(_cache!!.cachePager.getResults(), false);
}
fun setPager(pager: TPager, cache: ItemCache? = null) {
- synchronized(_pager_lock) {
+ synchronized(_pagerLock) {
detachParentPagerEvents();
detachPagerEvents();
@@ -425,7 +437,7 @@ abstract class FeedView : L
val p = recyclerData.pager;
if(p is IReplacerPager<*>) {
p.onReplaced.subscribe(this) { _, newItem ->
- synchronized(_pager_lock) {
+ synchronized(_pagerLock) {
val filtered = filterResults(listOf(newItem as TResult));
if(filtered.isEmpty())
return@subscribe;
@@ -443,7 +455,7 @@ abstract class FeedView : L
var _lastNextPage = false;
private fun loadNextPage() {
- synchronized(_pager_lock) {
+ synchronized(_pagerLock) {
val pager: TPager = recyclerData.pager ?: return;
val hasMorePages = pager.hasMorePages();
Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}");
@@ -468,7 +480,7 @@ abstract class FeedView : L
}
companion object {
- private val TAG = "FeedView";
+ private const val TAG = "FeedView";
}
abstract class ItemCache(val cachePager: IPager) {
diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt
index 3ddddbf0..4ac4557b 100644
--- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt
+++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt
@@ -6,7 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.GridLayoutManager
import com.futo.platformplayer.*
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
@@ -18,13 +18,9 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.logging.Logger
-import com.futo.platformplayer.models.SearchType
-import com.futo.platformplayer.states.AnnouncementType
-import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlatform
-import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.NoResultsView
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
@@ -32,11 +28,8 @@ import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.buttons.BigButton
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.time.OffsetDateTime
-import java.util.UUID
class HomeFragment : MainFragment() {
override val isMainView : Boolean = true;
@@ -44,7 +37,7 @@ class HomeFragment : MainFragment() {
override val hasBottomBar: Boolean get() = true;
private var _view: HomeView? = null;
- private var _cachedRecyclerData: FeedView.RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null;
+ private var _cachedRecyclerData: FeedView.RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null;
fun reloadFeed() {
_view?.reloadFeed()
@@ -101,15 +94,19 @@ class HomeFragment : MainFragment() {
class HomeView : ContentFeedView {
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
- private var _announcementsView: AnnouncementView;
+ private var _announcementsView: AnnouncementView = AnnouncementView(context, null).apply {
+ if(!this.isClosed()) {
+ recyclerData.adapter.viewsToPrepend.add(this)
+ this.onClose.subscribe {
+ recyclerData.adapter.viewsToPrepend.remove(this)
+ }
+ }
+ };
private val _taskGetPager: TaskHandler>;
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
- constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) {
- _announcementsView = AnnouncementView(context, null).apply {
- headerView.addView(this);
- };
+ constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) {
_taskGetPager = TaskHandler>({ fragment.lifecycleScope }, {
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
@@ -174,7 +171,7 @@ class HomeFragment : MainFragment() {
loadResults();
}
- override fun getEmptyPagerView(): View? {
+ override fun getEmptyPagerView(): View {
val dp10 = 10.dp(resources);
val dp30 = 30.dp(resources);
@@ -206,8 +203,7 @@ class HomeFragment : MainFragment() {
listOf(BigButton(context, "Sources", "Go to the sources tab", R.drawable.ic_creators) {
fragment.navigate();
}.withMargin(dp10, dp30))
- );
- return null;
+ )
}
override fun reload() {
@@ -227,7 +223,7 @@ class HomeFragment : MainFragment() {
//StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), context.getString(R.string.no_home_available), context.getString(R.string.no_home_page_is_available_please_check_if_you_are_connected_to_the_internet_and_refresh), AnnouncementType.SESSION);
}
- Logger.i(TAG, "Got new home pager ${pager}");
+ Logger.i(TAG, "Got new home pager $pager");
finishRefreshLayoutLoader();
setLoading(false);
setPager(pager);
@@ -237,7 +233,7 @@ class HomeFragment : MainFragment() {
}
companion object {
- val TAG = "HomeFragment";
+ const val TAG = "HomeFragment";
fun newInstance() = HomeFragment().apply {}
}
diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt
index 05f064aa..b1d630e4 100644
--- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt
+++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt
@@ -5,12 +5,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.LinearLayout
import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.GridLayoutManager
import com.futo.platformplayer.*
import com.futo.platformplayer.activities.MainActivity
-import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
@@ -47,7 +45,6 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
-import java.nio.channels.Channel
import java.time.OffsetDateTime
import kotlin.system.measureTimeMillis
@@ -58,7 +55,7 @@ class SubscriptionsFeedFragment : MainFragment() {
private var _view: SubscriptionsFeedView? = null;
private var _group: SubscriptionGroup? = null;
- private var _cachedRecyclerData: FeedView.RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null;
+ private var _cachedRecyclerData: FeedView.RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null;
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
@@ -111,7 +108,7 @@ class SubscriptionsFeedFragment : MainFragment() {
var subGroup: SubscriptionGroup? = null;
- constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) {
+ constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) {
Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total ->
};
@@ -152,16 +149,19 @@ class SubscriptionsFeedFragment : MainFragment() {
val homeTab = Settings.instance.tabs.find { it.id == 0 };
val isHomeEnabled = homeTab?.enabled == true;
if (announcementsView != null && isHomeEnabled) {
- headerView.removeView(announcementsView);
- _announcementsView = null;
+ recyclerData.adapter.viewsToPrepend.remove(announcementsView)
+ _announcementsView = null
}
if (announcementsView == null && !isHomeEnabled) {
val c = context;
if (c != null) {
_announcementsView = AnnouncementView(c, null).apply {
- headerView.addView(this)
- };
+ recyclerData.adapter.viewsToPrepend.add(this)
+ this.onClose.subscribe {
+ recyclerData.adapter.viewsToPrepend.remove(this)
+ }
+ }
}
}
@@ -215,7 +215,7 @@ class SubscriptionsFeedFragment : MainFragment() {
val subRequestCounts = StateSubscriptions.instance.getSubscriptionRequestCount(group);
val reqCountStr = subRequestCounts.map { " ${it.key.config.name}: ${it.value}/${it.key.getSubscriptionRateLimit()}" }.joinToString("\n");
val rateLimitPlugins = subRequestCounts.filter { clientCount -> clientCount.key.getSubscriptionRateLimit()?.let { rateLimit -> clientCount.value > rateLimit } == true }
- Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n" + reqCountStr);
+ Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n$reqCountStr");
if(rateLimitPlugins.any())
throw RateLimitException(rateLimitPlugins.map { it.key.id });
}
@@ -277,7 +277,7 @@ class SubscriptionsFeedFragment : MainFragment() {
private fun initializeToolbarContent() {
_subscriptionBar = SubscriptionBar(context).apply {
- layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
};
_subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate(c); };
_subscriptionBar?.onToggleGroup?.subscribe { g ->
@@ -397,7 +397,7 @@ class SubscriptionsFeedFragment : MainFragment() {
_taskGetPager.run(withRefetch);
}
- override fun onRestoreCachedData(cachedData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) {
+ override fun onRestoreCachedData(cachedData: RecyclerData, GridLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) {
super.onRestoreCachedData(cachedData);
setEmptyPager(cachedData.results.isEmpty());
}
@@ -452,7 +452,7 @@ class SubscriptionsFeedFragment : MainFragment() {
if (toShow is PluginException)
UIDialogs.appToast(ToastView.Toast(
toShow.message +
- (if(channel != null) "\nChannel: " + channel else ""), false, null,
+ (if(channel != null) "\nChannel: $channel" else ""), false, null,
"Plugin ${toShow.config.name} failed")
);
else
@@ -463,14 +463,14 @@ class SubscriptionsFeedFragment : MainFragment() {
val failedChannels = exs.filterIsInstance().map { it.channelNameOrUrl }.distinct().toList();
val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) }
.map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null }
- .filter { it != null }
+ .filterNotNull()
.distinctBy { it?.config?.name }
.map { it!! }
.toList();
for(distinctPluginFail in failedPlugins)
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
if(failedChannels.isNotEmpty())
- UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- ${it}" }.joinToString("\n") +
+ UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- $it" }.joinToString("\n") +
(if(failedChannels.size >= 3) "\nAnd ${failedChannels.size - 3} more" else ""), false, null, "Failed Channels"));
}
} catch (e: Throwable) {
@@ -482,7 +482,7 @@ class SubscriptionsFeedFragment : MainFragment() {
}
companion object {
- val TAG = "SubscriptionsFeedFragment";
+ const val TAG = "SubscriptionsFeedFragment";
fun newInstance() = SubscriptionsFeedFragment().apply {}
}
diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
index 9fddac25..45c1d0a2 100644
--- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
+++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
+import android.widget.ScrollView
import android.widget.TextView
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.IPlatformClient
@@ -58,7 +59,15 @@ class TutorialFragment : MainFragment() {
}
@SuppressLint("ViewConstructor")
- class TutorialView : LinearLayout {
+ class TutorialView(fragment: TutorialFragment, inflater: LayoutInflater) :
+ ScrollView(inflater.context) {
+ init {
+ addView(TutorialContainer(fragment, inflater))
+ }
+ }
+
+ @SuppressLint("ViewConstructor")
+ class TutorialContainer : LinearLayout {
val fragment: TutorialFragment
constructor(fragment: TutorialFragment, inflater: LayoutInflater) : super(inflater.context) {
@@ -150,7 +159,7 @@ class TutorialFragment : MainFragment() {
}
companion object {
- val TAG = "HomeFragment";
+ const val TAG = "HomeFragment";
fun newInstance() = TutorialFragment().apply {}
val initialSetupVideos = listOf(
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 233d9dc4..ea7bf5f1 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
@@ -1,11 +1,10 @@
package com.futo.platformplayer.fragment.mainactivity.main
+import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
-import android.os.Handler
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -14,10 +13,9 @@ import android.view.WindowInsetsController
import android.view.WindowManager
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.WindowCompat
-import androidx.lifecycle.lifecycleScope
+import androidx.media3.common.util.UnstableApi
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
-import com.futo.platformplayer.SimpleOrientationListener
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
@@ -25,14 +23,14 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
-import com.futo.platformplayer.listeners.AutoRotateChangeListener
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.PlatformVideoWithTime
import com.futo.platformplayer.models.UrlVideoWithTime
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
+import kotlin.math.min
-
+@UnstableApi
class VideoDetailFragment : MainFragment {
override val isMainView : Boolean = false;
override val hasBottomBar: Boolean = true;
@@ -43,11 +41,13 @@ class VideoDetailFragment : MainFragment {
private var _viewDetail : VideoDetailView? = null;
private var _view : SingleViewTouchableMotionLayout? = null;
- private lateinit var _autoRotateChangeListener: AutoRotateChangeListener
- private lateinit var _orientationListener: SimpleOrientationListener
- private var _currentOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
var isFullscreen : Boolean = false;
+ /**
+ * whether the view is in the process of switching from full-screen maximized to minimized
+ * this is used to detect that the app is skipping the non full-screen maximized state
+ */
+ var isMinimizingFromFullScreen : Boolean = false;
val onFullscreenChanged = Event1();
var isTransitioning : Boolean = false
private set;
@@ -77,8 +77,7 @@ class VideoDetailFragment : MainFragment {
private var _leavingPiP = false;
//region Fragment
- constructor() : super() {
- }
+ constructor() : super()
fun nextVideo() {
_viewDetail?.nextVideo(true, true, true);
@@ -88,65 +87,105 @@ class VideoDetailFragment : MainFragment {
_viewDetail?.prevVideo(true);
}
- private fun onStateChanged(state: VideoDetailFragment.State) {
+ private fun isSmallWindow(): Boolean {
+ return min(
+ resources.configuration.screenWidthDp,
+ resources.configuration.screenHeightDp
+ ) < resources.getDimension(R.dimen.landscape_threshold)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+
+ val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
+
+ val isSmallWindow = isSmallWindow()
+
+ if (
+ isSmallWindow
+ && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+ && !isFullscreen
+ && state == State.MAXIMIZED
+ ) {
+ _viewDetail?.setFullscreen(true)
+ } else if (
+ isSmallWindow
+ && isFullscreen
+ && !Settings.instance.playback.fullscreenPortrait
+ && newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+ && isLandscapeVideo
+ ) {
+ _viewDetail?.setFullscreen(false)
+ }
+ }
+
+ private fun onStateChanged(state: State) {
+ if (
+ isSmallWindow()
+ && state == State.MAXIMIZED
+ && !isFullscreen
+ && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ ) {
+ _viewDetail?.setFullscreen(true)
+ }
+
updateOrientation()
}
- private fun updateOrientation() {
+ private fun onVideoChanged(videoWidth : Int, videoHeight: Int) {
+ if (
+ isSmallWindow()
+ && state == State.MAXIMIZED
+ && !isFullscreen
+ && videoHeight > videoWidth
+ ) {
+ _viewDetail?.setFullscreen(true)
+ }
+ }
+
+ @SuppressLint("SourceLockedOrientationActivity")
+ fun updateOrientation() {
val a = activity ?: return
- 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)
- currentOrientation = currentRequestedOrientation
+ val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait
+ val isReversePortraitAllowed = Settings.instance.playback.reversePortrait
+ val rotationLock = StatePlayer.instance.rotationLock
- val isAutoRotate = Settings.instance.playback.isAutoRotate()
- val isFs = isFullscreen
+ val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
- 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_PORTRAIT
- }
+ val isSmallWindow = isSmallWindow()
+
+ // 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 && isLandscapeVideo) {
+ 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 && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ } else if (rotationLock) {
+ a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
} else {
- 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
+ when (Settings.instance.playback.autoRotate) {
+ 0 -> {
+ a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
+ }
+
+ 1 -> {
+ a.requestedOrientation = if (isReversePortraitAllowed) {
+ ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ } else {
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ }
+ }
+
+ 2 -> {
+ a.requestedOrientation = if (isReversePortraitAllowed) {
+ ActivityInfo.SCREEN_ORIENTATION_FULL_USER
+ } else {
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ }
}
- } 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}, fullAutorotateLock = ${fullAutorotateLock}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}");
}
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
@@ -188,10 +227,6 @@ class VideoDetailFragment : MainFragment {
return true;
}
- override fun onHide() {
- super.onHide();
- }
-
fun preventPictureInPicture() {
Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true");
_viewDetail?.preventPictureInPicture = true;
@@ -231,7 +266,9 @@ class VideoDetailFragment : MainFragment {
_viewDetail = _view!!.findViewById(R.id.fragview_videodetail).also {
it.applyFragment(this);
it.onFullscreenChanged.subscribe(::onFullscreenChanged);
+ it.onVideoChanged.subscribe(::onVideoChanged)
it.onMinimize.subscribe {
+ isMinimizingFromFullScreen = true
_view!!.transitionToStart();
};
it.onClose.subscribe {
@@ -268,6 +305,7 @@ class VideoDetailFragment : MainFragment {
if (state != State.MINIMIZED && progress < 0.1) {
state = State.MINIMIZED;
+ isMinimizingFromFullScreen = false
onMinimize.emit();
}
else if (state != State.MAXIMIZED && progress > 0.9) {
@@ -306,13 +344,6 @@ class VideoDetailFragment : MainFragment {
minimizeVideoDetail();
}
- _autoRotateChangeListener = AutoRotateChangeListener(requireContext(), Handler()) { _ ->
- if (updateAutoFullscreen()) {
- return@AutoRotateChangeListener
- }
- updateOrientation()
- }
-
_loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) };
maximizeVideoDetail();
@@ -321,40 +352,11 @@ class VideoDetailFragment : MainFragment {
}
StatePlayer.instance.onRotationLockChanged.subscribe(this) {
- if (updateAutoFullscreen()) {
- return@subscribe
- }
- updateOrientation()
- }
-
- _orientationListener = SimpleOrientationListener(requireActivity(), lifecycleScope)
- _orientationListener.onOrientationChanged.subscribe {
- _currentOrientation = it
- Logger.i(TAG, "Current orientation changed (_currentOrientation = ${_currentOrientation})")
-
- if (updateAutoFullscreen()) {
- return@subscribe
- }
updateOrientation()
}
return _view!!;
}
- private fun updateAutoFullscreen(): Boolean {
- if (Settings.instance.playback.isAutoRotate()) {
- if (state == State.MAXIMIZED && !isFullscreen && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)) {
- _viewDetail?.setFullscreen(true)
- return true
- }
-
- if (state == State.MAXIMIZED && isFullscreen && !Settings.instance.playback.fullscreenPortrait && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || (Settings.instance.playback.reversePortrait && _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT))) {
- _viewDetail?.setFullscreen(false)
- return true
- }
- }
- return false
- }
-
fun onUserLeaveHint() {
val viewDetail = _viewDetail;
Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}");
@@ -443,15 +445,12 @@ class VideoDetailFragment : MainFragment {
if(shouldStop) {
_viewDetail?.onStop();
StateCasting.instance.onStop();
- Logger.v(TAG, "called onStop() shouldStop: $shouldStop");
}
}
override fun onDestroyMainView() {
super.onDestroyMainView();
Logger.v(TAG, "onDestroyMainView");
- _autoRotateChangeListener?.unregister()
- _orientationListener.stopListening()
SettingsActivity.settingsActivityClosed.remove(this)
StatePlayer.instance.onRotationLockChanged.remove(this)
@@ -532,7 +531,7 @@ class VideoDetailFragment : MainFragment {
}
companion object {
- private val TAG = "VideoDetailFragment";
+ private const val TAG = "VideoDetailFragment";
fun newInstance() = VideoDetailFragment().apply {}
}
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 d2bbf1b5..a3f403e6 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
@@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.Context
import android.content.Intent
+import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Rect
@@ -81,6 +82,7 @@ import com.futo.platformplayer.casting.CastConnectionState
import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
+import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.dp
@@ -159,20 +161,20 @@ import com.futo.polycentric.core.Opinion
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.protobuf.ByteString
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
-import okhttp3.Dispatcher
-import org.w3c.dom.Text
import userpackage.Protocol
import java.time.OffsetDateTime
import kotlin.math.abs
+import kotlin.math.max
import kotlin.math.roundToLong
-@androidx.media3.common.util.UnstableApi
+@UnstableApi
class VideoDetailView : ConstraintLayout {
private val TAG = "VideoDetailView"
@@ -185,7 +187,7 @@ class VideoDetailView : ConstraintLayout {
private var _searchVideo: IPlatformVideo? = null;
var video: IPlatformVideoDetails? = null
private set;
- var videoLocal: VideoLocal? = null;
+ private var videoLocal: VideoLocal? = null;
private var _playbackTracker: IPlaybackTracker? = null;
private var _historyIndex: DBHistory.Index? = null;
@@ -200,7 +202,7 @@ class VideoDetailView : ConstraintLayout {
private val _timeBar: TimeBar;
private var _upNext: UpNextView;
- val rootView: ConstraintLayout;
+ private val rootView: ConstraintLayout;
private val _title: TextView;
private val _subTitle: TextView;
@@ -289,7 +291,7 @@ class VideoDetailView : ConstraintLayout {
var isPlaying: Boolean = false
private set;
- var lastPositionMilliseconds: Long = 0
+ private var lastPositionMilliseconds: Long = 0
private set;
private var _historicalPosition: Long = 0;
private var _commentsCount = 0;
@@ -304,6 +306,7 @@ class VideoDetailView : ConstraintLayout {
val onFullscreenChanged = Event1();
val onEnterPictureInPicture = Event0();
val onPlayChanged = Event1();
+ val onVideoChanged = Event2()
var allowBackground : Boolean = false
private set;
@@ -529,12 +532,14 @@ class VideoDetailView : ConstraintLayout {
_cast.onChapterChanged.subscribe(onChapterChanged);
_cast.onMinimizeClick.subscribe {
- _player.setFullScreen(false);
- onMinimize.emit();
+ // emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
+ onMinimize.emit()
+ _player.setFullScreen(false)
};
_player.onMinimize.subscribe {
- _player.setFullScreen(false);
- onMinimize.emit();
+ // emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
+ onMinimize.emit()
+ _player.setFullScreen(false)
};
_player.onTimeBarChanged.subscribe { position, _ ->
@@ -723,7 +728,8 @@ class VideoDetailView : ConstraintLayout {
if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c;
- _container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, c.contextUrl, c.reference, c,
+ _container_content_replies.load(
+ _tabIndex!! != 0, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
@@ -731,7 +737,7 @@ class VideoDetailView : ConstraintLayout {
parentComment = newComment;
});
} else {
- _container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
+ _container_content_replies.load(_tabIndex!! != 0, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
}
switchContentView(_container_content_replies);
};
@@ -941,7 +947,7 @@ class VideoDetailView : ConstraintLayout {
else {
val selectedButtons = _buttonPinStore.getAllValues()
.map { x-> buttons.find { it.tagRef == x } }
- .filter { it != null }
+ .filterNotNull()
.map { it!! };
_buttonPins.setButtons(*(selectedButtons +
buttons.filter { !selectedButtons.contains(it) } +
@@ -1257,7 +1263,8 @@ class VideoDetailView : ConstraintLayout {
switchContentView(_container_content_main);
}
- //@OptIn(ExperimentalCoroutinesApi::class)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
_didTriggerDatasourceErrroCount = 0;
@@ -1265,7 +1272,7 @@ class VideoDetailView : ConstraintLayout {
_autoplayVideo = null
Logger.i(TAG, "Autoplay video cleared (setVideoDetails)")
- if(newVideo && this.video?.url == videoDetail.url)
+ if (newVideo && this.video?.url == videoDetail.url)
return;
if (newVideo) {
@@ -1274,8 +1281,13 @@ class VideoDetailView : ConstraintLayout {
_lastSubtitleSource = null;
}
- if(videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
- UIDialogs.toast(context, context.getString(R.string.planned_in) + " ${videoDetail.datetime?.toHumanNowDiffString(true)}")
+ if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
+ UIDialogs.toast(
+ context,
+ context.getString(R.string.planned_in) + " ${
+ videoDetail.datetime?.toHumanNowDiffString(true)
+ }"
+ )
if (!videoDetail.isLive) {
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
@@ -1284,26 +1296,25 @@ class VideoDetailView : ConstraintLayout {
val videoLocal: VideoLocal?;
val video: IPlatformVideoDetails?;
- if(videoDetail is VideoLocal) {
+ if (videoDetail is VideoLocal) {
videoLocal = videoDetail;
video = videoDetail;
this.video = video;
val videoTask = StatePlatform.instance.getContentDetails(videoDetail.url);
videoTask.invokeOnCompletion { ex ->
- if(ex != null) {
+ if (ex != null) {
Logger.e(TAG, "Failed to fetch live video for offline video", ex);
return@invokeOnCompletion;
}
val result = videoTask.getCompleted();
- if(this.video == videoDetail && result is IPlatformVideoDetails) {
+ if (this.video == videoDetail && result is IPlatformVideoDetails) {
this.video = result;
fragment.lifecycleScope.launch(Dispatchers.Main) {
updateQualitySourcesOverlay(result, videoLocal);
}
}
};
- }
- else { //TODO: Update cached video if it exists with video
+ } else { //TODO: Update cached video if it exists with video
videoLocal = StateDownloads.instance.getCachedVideo(videoDetail.id);
video = videoDetail;
}
@@ -1311,7 +1322,9 @@ class VideoDetailView : ConstraintLayout {
this.video = video;
cleanupPlaybackTracker();
- if(video is JSVideoDetails) {
+ onVideoChanged.emit(video.video.videoSources[0].width, video.video.videoSources[0].height)
+
+ if (video is JSVideoDetails) {
val me = this;
fragment.lifecycleScope.launch(Dispatchers.IO) {
try {
@@ -1319,8 +1332,7 @@ class VideoDetailView : ConstraintLayout {
val chapters = null ?: StatePlatform.instance.getContentChapters(video.url);
_player.setChapters(chapters);
_cast.setChapters(chapters);
- }
- catch(ex: Throwable) {
+ } catch (ex: Throwable) {
Logger.e(TAG, "Failed to get chapters", ex);
_player.setChapters(null);
_cast.setChapters(null);
@@ -1330,7 +1342,7 @@ class VideoDetailView : ConstraintLayout {
}*/
}
try {
- if(!StateApp.instance.privateMode) {
+ if (!StateApp.instance.privateMode) {
val stopwatch = com.futo.platformplayer.debug.Stopwatch()
var tracker = video.getPlaybackTracker()
Logger.i(TAG, "video.getPlaybackTracker took ${stopwatch.elapsedMs}ms")
@@ -1346,17 +1358,20 @@ class VideoDetailView : ConstraintLayout {
if (me.video == video)
me._playbackTracker = tracker;
- }
- else if(me.video == video)
+ } else if (me.video == video)
me._playbackTracker = null;
- }
- catch(ex: Throwable) {
+ } catch (ex: Throwable) {
Logger.e(TAG, "Playback tracker failed", ex);
+
if(me.video?.isLive == true || ex.message?.contains("Unable to resolve host") == true) withContext(Dispatchers.Main) {
UIDialogs.toast(context, context.getString(R.string.failed_to_get_playback_tracker));
};
else withContext(Dispatchers.Main) {
- UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_get_playback_tracker), ex);
+ UIDialogs.showGeneralErrorDialog(
+ context,
+ context.getString(R.string.failed_to_get_playback_tracker),
+ ex
+ );
}
}
};
@@ -1373,8 +1388,11 @@ class VideoDetailView : ConstraintLayout {
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, true) else setTabIndex(1, true);
+ when (Settings.instance.comments.defaultCommentSection) {
+ 0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex(
+ 0,
+ true
+ ) else setTabIndex(1, true);
1 -> setTabIndex(1, true);
2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true)
}
@@ -1384,9 +1402,16 @@ class VideoDetailView : ConstraintLayout {
//UI
_title.text = video.name;
_channelName.text = video.author.name;
- if(video.author.subscribers != null) {
- _channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
- (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0);
+ if (video.author.subscribers != null) {
+ _channelMeta.text = if ((video.author.subscribers
+ ?: 0) > 0
+ ) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
+ (_channelName.layoutParams as MarginLayoutParams).setMargins(
+ 0,
+ (DP_5 * -1).toInt(),
+ 0,
+ 0
+ );
} else {
_channelMeta.text = "";
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0);
@@ -1394,7 +1419,7 @@ class VideoDetailView : ConstraintLayout {
video.author.let {
- if(it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
+ if (it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
_monetization.setPlatformMembership(video.id.pluginId, it.membershipUrl);
else
_monetization.setPlatformMembership(null, null);
@@ -1408,7 +1433,8 @@ class VideoDetailView : ConstraintLayout {
_creatorThumbnail.setThumbnail(video.author.thumbnail, false);
- val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url, true);
+ val cachedPolycentricProfile =
+ PolycentricCache.instance.getCachedProfile(video.author.url, true);
if (cachedPolycentricProfile != null) {
setPolycentricProfile(cachedPolycentricProfile, animate = false);
} else {
@@ -1417,13 +1443,19 @@ class VideoDetailView : ConstraintLayout {
}
_platform.setPlatformFromClientID(video.id.pluginId);
- val subTitleSegments : ArrayList = ArrayList();
- if(video.viewCount > 0)
- subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) context.getString(R.string.watching_now) else context.getString(R.string.views)}");
- if(video.datetime != null) {
+ val subTitleSegments: ArrayList = ArrayList();
+ if (video.viewCount > 0)
+ subTitleSegments.add(
+ "${video.viewCount.toHumanNumber()} ${
+ if (video.isLive) context.getString(
+ R.string.watching_now
+ ) else context.getString(R.string.views)
+ }"
+ );
+ if (video.datetime != null) {
val diff = video.datetime?.getNowDiffSeconds() ?: 0;
val ago = video.datetime?.toHumanNowDiffString(true)
- if(diff >= 0)
+ if (diff >= 0)
subTitleSegments.add("${ago} ago");
else
subTitleSegments.add("available in ${ago}");
@@ -1436,20 +1468,27 @@ class VideoDetailView : ConstraintLayout {
fragment.lifecycleScope.launch(Dispatchers.IO) {
try {
- val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null,
+ val queryReferencesResponse = ApiMethods.getQueryReferences(
+ PolycentricCache.SERVER, ref, null, null,
arrayListOf(
- Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
- ByteString.copyFrom(Opinion.like.data)).build(),
- Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
- ByteString.copyFrom(Opinion.dislike.data)).build()
+ Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
+ .setFromType(ContentType.OPINION.value).setValue(
+ ByteString.copyFrom(Opinion.like.data)
+ ).build(),
+ Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
+ .setFromType(ContentType.OPINION.value).setValue(
+ ByteString.copyFrom(Opinion.dislike.data)
+ ).build()
),
extraByteReferences = listOfNotNull(extraBytesRef)
);
val likes = queryReferencesResponse.countsList[0];
val dislikes = queryReferencesResponse.countsList[1];
- val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
- val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
+ val hasLiked =
+ StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
+ val hasDisliked =
+ StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
withContext(Dispatchers.Main) {
_rating.visibility = View.VISIBLE;
@@ -1473,7 +1512,11 @@ class VideoDetailView : ConstraintLayout {
}
}
- StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked)
+ StatePolycentric.instance.updateLikeMap(
+ ref,
+ args.hasLiked,
+ args.hasDisliked
+ )
};
}
} catch (e: Throwable) {
@@ -1495,6 +1538,7 @@ class VideoDetailView : ConstraintLayout {
_textDislikes.visibility = View.VISIBLE;
_textDislikes.text = r.dislikes.toHumanNumber();
}
+
is RatingLikes -> {
val r = video.rating as RatingLikes;
_layoutRating.visibility = View.VISIBLE;
@@ -1506,6 +1550,7 @@ class VideoDetailView : ConstraintLayout {
_imageDislikeIcon.visibility = View.GONE;
_textDislikes.visibility = View.GONE;
}
+
else -> {
_layoutRating.visibility = View.GONE;
}
@@ -1517,6 +1562,7 @@ class VideoDetailView : ConstraintLayout {
setLoading(false);
+
//Set Mediasource
val toResume = _videoResumePositionMilliseconds;
@@ -1533,9 +1579,22 @@ class VideoDetailView : ConstraintLayout {
val historyItem = getHistoryIndex(videoDetail) ?: return@launch;
withContext(Dispatchers.Main) {
- _historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,false, (toResume.toFloat() / 1000.0f).toLong(), null, true);
- Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds");
- if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) {
+ _historicalPosition = StateHistory.instance.updateHistoryPosition(
+ video,
+ historyItem,
+ false,
+ (toResume.toFloat() / 1000.0f).toLong(),
+ null,
+ true
+ );
+ Logger.i(
+ TAG,
+ "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"
+ );
+ if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(
+ _historicalPosition - lastPositionMilliseconds / 1000
+ ) > 5.0
+ ) {
_layoutResume.visibility = View.VISIBLE;
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
@@ -1561,10 +1620,10 @@ class VideoDetailView : ConstraintLayout {
_liveChat?.stop();
_liveChat = null;
- if(video.isLive && video.live != null) {
+ if (video.isLive && video.live != null) {
loadLiveChat(video);
}
- if(video.isLive && video.live == null && !video.video.videoSources.any())
+ if (video.isLive && video.live == null && !video.video.videoSources.any())
startLiveTry(video);
@@ -1945,7 +2004,7 @@ class VideoDetailView : ConstraintLayout {
?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) }
?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource }))
?.distinct()
- ?.filter { it != null }
+ ?.filterNotNull()
?.toList() ?: listOf() else videoSources?.toList() ?: listOf()
val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container };
val bestAudioSources = if(doDedup) audioSources
@@ -2246,7 +2305,7 @@ class VideoDetailView : ConstraintLayout {
cleanupPlaybackTracker();
val url = _url;
- if (url != null && url.isNotBlank()) {
+ if (!url.isNullOrBlank()) {
setLoading(true);
_taskLoadVideo.run(url);
}
@@ -2258,7 +2317,7 @@ class VideoDetailView : ConstraintLayout {
if(fullscreen) {
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
- val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
+ val lp = _container_content.layoutParams as LayoutParams;
lp.topMargin = 0;
_container_content.layoutParams = lp;
@@ -2271,7 +2330,7 @@ class VideoDetailView : ConstraintLayout {
else {
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
- val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
+ val lp = _container_content.layoutParams as LayoutParams;
lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt();
_container_content.layoutParams = lp;
@@ -2312,9 +2371,20 @@ class VideoDetailView : ConstraintLayout {
}
}
+ fun isLandscapeVideo(): Boolean? {
+ var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width
+ var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height
+
+ return if (videoSourceWidth == null || videoSourceHeight == null || videoSourceWidth == 0 || videoSourceHeight == 0){
+ null
+ } else{
+ videoSourceWidth >= videoSourceHeight
+ }
+ }
+
fun setFullscreen(fullscreen : Boolean) {
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
- _player.setFullScreen(fullscreen);
+ _player.setFullScreen(fullscreen)
}
private fun setLoading(isLoading : Boolean) {
if(isLoading){
@@ -2505,7 +2575,8 @@ class VideoDetailView : ConstraintLayout {
_overlayContainer.removeAllViews();
_overlay_quality_selector?.hide();
- _player.fillHeight();
+ _player.setFullScreen(true)
+ _player.fillHeight(false)
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
}
fun handleLeavePictureInPicture() {
@@ -2641,7 +2712,7 @@ class VideoDetailView : ConstraintLayout {
else {
if(_player.layoutParams.height == WRAP_CONTENT) {
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
- _player.fillHeight();
+ _player.fillHeight(true)
_cast.layoutParams = _cast.layoutParams.apply {
(this as MarginLayoutParams).bottomMargin = 0;
};
@@ -2714,13 +2785,24 @@ class VideoDetailView : ConstraintLayout {
if(_minimize_controls.isClickable != clickable)
_minimize_controls.isClickable = clickable;
}
- fun setVideoMinimize(value : Float) {
- val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt();
- _player.setPadding(0, _player.paddingTop, padRight, 0);
- _cast.setPadding(0, _cast.paddingTop, padRight, 0);
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+ if (fragment.state == VideoDetailFragment.State.MINIMIZED) {
+ _player.fillHeight(true)
+ } else if (!fragment.isFullscreen) {
+ _player.fitHeight()
+ }
}
- fun setTopPadding(value : Float) {
- _player.setPadding(0, value.toInt(), _player.paddingRight, 0);
+
+ fun setVideoMinimize(value : Float) {
+ val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt()
+ _player.setPadding(0, _player.paddingTop, padRight, 0)
+ _cast.setPadding(0, _cast.paddingTop, padRight, 0)
+ }
+
+ fun setTopPadding(value: Float) {
+ _player.setPadding(_player.paddingLeft, value.toInt(), _player.paddingRight, 0)
}
//Tasks
diff --git a/app/src/main/java/com/futo/platformplayer/listeners/AutoRotateChangeListener.kt b/app/src/main/java/com/futo/platformplayer/listeners/AutoRotateChangeListener.kt
deleted file mode 100644
index 12efb3aa..00000000
--- a/app/src/main/java/com/futo/platformplayer/listeners/AutoRotateChangeListener.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.futo.platformplayer.listeners
-
-import android.content.Context
-import android.database.ContentObserver
-import android.os.Handler
-import android.provider.Settings
-
-class AutoRotateObserver(handler: Handler, private val onChangeCallback: () -> Unit) : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean) {
- super.onChange(selfChange)
- onChangeCallback()
- }
-}
-
-class AutoRotateChangeListener(context: Context, handler: Handler, private val onAutoRotateChanged: (Boolean) -> Unit) {
-
- private val contentResolver = context.contentResolver
- private val autoRotateObserver = AutoRotateObserver(handler) {
- val isAutoRotateEnabled = isAutoRotateEnabled()
- onAutoRotateChanged(isAutoRotateEnabled)
- }
-
- init {
- contentResolver.registerContentObserver(
- Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
- false,
- autoRotateObserver
- )
- }
-
- fun unregister() {
- contentResolver.unregisterContentObserver(autoRotateObserver)
- }
-
- private fun isAutoRotateEnabled(): Boolean {
- return Settings.System.getInt(
- contentResolver,
- Settings.System.ACCELEROMETER_ROTATION,
- 0
- ) == 1
- }
-}
diff --git a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt
index 7767265f..467f23c4 100644
--- a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt
@@ -10,6 +10,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R
+import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.dp
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.Announcement
@@ -35,6 +36,8 @@ class AnnouncementView : LinearLayout {
private val _category: String?;
private var _currentAnnouncement: Announcement? = null;
+ val onClose = Event0();
+
private val _scope: CoroutineScope?;
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
@@ -101,6 +104,10 @@ class AnnouncementView : LinearLayout {
setAnnouncement(announcements.firstOrNull(), announcements.size);
}
+ fun isClosed(): Boolean{
+ return _currentAnnouncement == null
+ }
+
private fun setAnnouncement(announcement: Announcement?, count: Int) {
if(count == 0 && announcement == null)
Logger.i(TAG, "setAnnouncement announcement=$announcement count=$count");
@@ -108,11 +115,12 @@ class AnnouncementView : LinearLayout {
_currentAnnouncement = announcement;
if (announcement == null) {
- _root.visibility = View.GONE;
+ visibility = View.GONE
+ onClose.emit()
return;
}
- _root.visibility = View.VISIBLE;
+ visibility = View.VISIBLE
_textTitle.text = announcement.title;
_textBody.text = announcement.msg;
diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
index 3f465269..76a8a0f7 100644
--- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt
@@ -20,7 +20,6 @@ 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
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
@@ -111,7 +110,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private val _author_fullscreen: TextView;
private var _shouldRestartHideJobOnPlaybackStateChange: Boolean = false;
- private var _lastSourceFit: Int? = null;
+ private var _lastSourceFit: Float? = null;
+ private var _lastWindowWidth: Int = resources.configuration.screenWidthDp
+ private var _lastWindowHeight: Int = resources.configuration.screenHeightDp
private var _originalBottomMargin: Int = 0;
private var _isControlsLocked: Boolean = false;
@@ -632,7 +633,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private fun fitOrFill(fullScreen: Boolean) {
if (fullScreen) {
- fillHeight();
+ fillHeight(false);
} else {
fitHeight();
}
@@ -655,7 +656,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
gestureControl.resetZoomPan()
_lastSourceFit = null;
if(isFullScreen)
- fillHeight();
+ fillHeight(false);
else if(_root.layoutParams.height != MATCH_PARENT)
fitHeight(videoSize);
}
@@ -718,58 +719,73 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
//Sizing
@OptIn(UnstableApi::class)
- fun fitHeight(videoSize : VideoSize? = null){
- Logger.i(TAG, "Video Fit Height");
- if(_originalBottomMargin != 0) {
- val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
- layoutParams.setMargins(0, 0, 0, _originalBottomMargin);
- _videoView.layoutParams = layoutParams;
+ fun fitHeight(videoSize: VideoSize? = null) {
+ Logger.i(TAG, "Video Fit Height")
+ if (_originalBottomMargin != 0) {
+ val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams
+ layoutParams.setMargins(0, 0, 0, _originalBottomMargin)
+ _videoView.layoutParams = layoutParams
}
- var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height ?: 0;
- var w = videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0;
+ var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height
+ ?: 0
+ var w =
+ videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0
- if(h == 0 && w == 0) {
- Logger.i(TAG, "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})");
- w = 1280;
- h = 720;
+ if (h == 0 && w == 0) {
+ Logger.i(
+ TAG,
+ "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})"
+ );
+ w = 1280
+ h = 720
}
+ val configuration = resources.configuration
- if(_lastSourceFit == null){
- val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics;
+ val windowWidth = configuration.screenWidthDp
+ val windowHeight = configuration.screenHeightDp
- val viewWidth = Math.min(metrics.widthPixels, metrics.heightPixels); //TODO: Get parent width. was this.width
- val deviceHeight = Math.max(metrics.widthPixels, metrics.heightPixels);
- val maxHeight = deviceHeight * 0.4;
+ if (_lastSourceFit == null || windowWidth != _lastWindowWidth || windowHeight != _lastWindowHeight) {
+ val maxHeight = windowHeight * 0.4f
- val determinedHeight = if(w > h)
- ((h * (viewWidth.toDouble() / w)).toInt())
+ val aspectRatio = h.toFloat() / w
+ val determinedHeight = (aspectRatio * windowWidth)
+
+ _lastSourceFit = determinedHeight
+ _lastSourceFit = _lastSourceFit!!.coerceAtLeast(220f)
+ _lastSourceFit = _lastSourceFit!!.coerceAtMost(maxHeight)
+
+ _desiredResizeModePortrait = if (_lastSourceFit != determinedHeight)
+ AspectRatioFrameLayout.RESIZE_MODE_FIT
else
- ((h * (viewWidth.toDouble() / w)).toInt());
- _lastSourceFit = determinedHeight;
- _lastSourceFit = Math.max(_lastSourceFit!!, 250);
- _lastSourceFit = Math.min(_lastSourceFit!!, maxHeight.toInt());
- if((_lastSourceFit ?: 0) < 300 || (_lastSourceFit ?: 0) > viewWidth) {
- Log.d(TAG, "WEIRD HEIGHT DETECTED: ${_lastSourceFit}, Width: ${w}, Height: ${h}, VWidth: ${viewWidth}");
- }
- if(_lastSourceFit != determinedHeight)
- _desiredResizeModePortrait = AspectRatioFrameLayout.RESIZE_MODE_FIT;
- else
- _desiredResizeModePortrait = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
- _videoView.resizeMode = _desiredResizeModePortrait
- }
+ AspectRatioFrameLayout.RESIZE_MODE_ZOOM
- val marginBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt();
- val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom)
- rootParams.bottomMargin = marginBottom;
+ _lastWindowWidth = windowWidth
+ _lastWindowHeight = windowHeight
+ }
+ _videoView.resizeMode = _desiredResizeModePortrait
+
+ val marginBottom =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics)
+ .toInt()
+ val height = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ _lastSourceFit!!,
+ resources.displayMetrics
+ )
+ val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height + marginBottom).toInt())
+ rootParams.bottomMargin = marginBottom
_root.layoutParams = rootParams
- isFitMode = true;
+ isFitMode = true
}
- fun fillHeight(){
+
+ @OptIn(UnstableApi::class)
+ fun fillHeight(isMiniPlayer: Boolean) {
Logger.i(TAG, "Video Fill Height");
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
- _originalBottomMargin = if(layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin;
+ _originalBottomMargin =
+ if (layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin;
layoutParams.setMargins(0);
_videoView.layoutParams = layoutParams;
_videoView.invalidate();
@@ -777,6 +793,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
_root.layoutParams = rootParams;
_root.invalidate();
+
+ if(isMiniPlayer){
+ _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
+ }
+
isFitMode = false;
}
diff --git a/app/src/main/res/layout/activity_polycentric_create_profile.xml b/app/src/main/res/layout/activity_polycentric_create_profile.xml
index 198488fd..57226b68 100644
--- a/app/src/main/res/layout/activity_polycentric_create_profile.xml
+++ b/app/src/main/res/layout/activity_polycentric_create_profile.xml
@@ -1,5 +1,6 @@
-
+ app:srcCompat="@drawable/ic_back_thin_white_16dp" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:srcCompat="@drawable/ic_help" />
-
+ app:layout_constraintBottom_toBottomOf="parent">
-
-
-
-
-
-
-
-
-
-
-
+ android:background="@color/black">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimensions.xml b/app/src/main/res/values/dimensions.xml
index d3c299e7..5e75240b 100644
--- a/app/src/main/res/values/dimensions.xml
+++ b/app/src/main/res/values/dimensions.xml
@@ -1,5 +1,6 @@
-
+ 500dp
200dp
-
\ No newline at end of file
+ 300dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ad3a4c49..2b809271 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -259,7 +259,7 @@
Add a comment…
Dismiss
Scan a QR code to install
- Toggle fullscreen
+ Toggle full-screen
By
Signature
Valid
@@ -370,11 +370,11 @@
Brightness slider
Enable slide gesture to change brightness
Toggle full screen
- Enable swipe gesture to toggle fullscreen
+ Enable swipe gesture to toggle full screen
System brightness
Gesture controls adjust system brightness
Restore system brightness
- Restore system brightness when exiting fullscreen
+ Restore system brightness when exiting full screen
Enable zoom
Enable two finger pinch zoom gesture
Enable pan
@@ -382,7 +382,7 @@
System volume
Gesture controls adjust system volume
Live Chat Webview
- Fullscreen portrait
+ Full-screen portrait
Allow reverse portrait
Allow app to flip into reverse portrait
Rotation zone
@@ -396,12 +396,12 @@
Prefer Webm Audio Codecs
If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.
Allow video under cutout
- Allow video to go underneath the screen cutout in full-screen.\nMay require restart
+ Allow video to go underneath the screen cutout in full screen.\nMay require restart
Enable autoplay by default
Autoplay will be enabled by default whenever you watch a video
+ Allow full-screen portrait
Delete from WatchLater when watched
After you leave a video that you mostly watched, it will be removed from watch later.
- Allow fullscreen portrait
Switch to Audio in Background
Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter
Groups
@@ -853,7 +853,7 @@
Loop
Previous
Next
- Fullscreen
+ Full screen
Autoplay
Update spinner
Play