From f64efdc964486f9fb58364e39700781751bf5e2e Mon Sep 17 00:00:00 2001 From: Kai Date: Sat, 14 Dec 2024 14:44:40 -0600 Subject: [PATCH 1/4] deps --- app/src/stable/assets/sources/apple-podcast | 2 +- app/src/unstable/assets/sources/apple-podcasts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/apple-podcast b/app/src/stable/assets/sources/apple-podcast index 95f7a7ef..f79c7141 160000 --- a/app/src/stable/assets/sources/apple-podcast +++ b/app/src/stable/assets/sources/apple-podcast @@ -1 +1 @@ -Subproject commit 95f7a7ef444259d6001617f60340cc36a887bd53 +Subproject commit f79c7141bcb11464103abc56fd7be492fe8568ab diff --git a/app/src/unstable/assets/sources/apple-podcasts b/app/src/unstable/assets/sources/apple-podcasts index 95f7a7ef..f79c7141 160000 --- a/app/src/unstable/assets/sources/apple-podcasts +++ b/app/src/unstable/assets/sources/apple-podcasts @@ -1 +1 @@ -Subproject commit 95f7a7ef444259d6001617f60340cc36a887bd53 +Subproject commit f79c7141bcb11464103abc56fd7be492fe8568ab From 45ded8d38439efc8946570fbd9056de39829da76 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 16 Dec 2024 18:21:57 -0600 Subject: [PATCH 2/4] update MotionLayout code fix touch region bug move more pieces to xml upgrade full screen gesture improve mini player UI --- app/build.gradle | 2 +- .../platformplayer/activities/MainActivity.kt | 16 +- .../mainactivity/main/ChannelFragment.kt | 4 +- .../mainactivity/main/ContentFeedView.kt | 6 +- .../mainactivity/main/DownloadsFragment.kt | 2 +- .../mainactivity/main/HistoryFragment.kt | 2 +- .../mainactivity/main/VideoDetailFragment.kt | 176 ++++------ .../mainactivity/main/VideoDetailView.kt | 196 ++++------- .../views/behavior/GestureControlView.kt | 6 + .../views/containers/CustomMotionLayout.kt | 121 +++++++ .../SingleViewTouchableMotionLayout.kt | 94 ------ .../views/video/FutoVideoPlayer.kt | 27 +- app/src/main/res/layout/activity_main.xml | 1 - app/src/main/res/layout/fragment_feed.xml | 3 + .../main/res/layout/fragment_video_detail.xml | 30 -- .../main/res/layout/fragview_video_detail.xml | 74 ++-- app/src/main/res/layout/video_player_ui.xml | 3 +- .../main/res/layout/video_player_ui_bar.xml | 4 +- app/src/main/res/layout/video_view.xml | 9 +- app/src/main/res/xml/videodetail_scene.xml | 316 ++++++++---------- 20 files changed, 430 insertions(+), 662 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/views/containers/CustomMotionLayout.kt delete mode 100644 app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt delete mode 100644 app/src/main/res/layout/fragment_video_detail.xml diff --git a/app/build.gradle b/app/build.gradle index 866a47dd..395d664c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -162,7 +162,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.11.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' //Images annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' 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 371b4d1b..12fe8ede 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -339,14 +339,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _fragVideoDetail.onMinimize.subscribe { updateSegmentPaddings(); }; - _fragVideoDetail.onTransitioning.subscribe { - if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED) - _fragContainerOverlay.elevation = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics); - else - _fragContainerOverlay.elevation = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); - } _fragVideoDetail.onCloseEvent.subscribe { _fragMainHome.setPreviewsEnabled(true); @@ -1076,8 +1068,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { if (_fragContainerVideoDetail.visibility != View.VISIBLE) _fragContainerVideoDetail.visibility = View.VISIBLE; when (segment.state) { - VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail() - VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail() + VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail(false) + VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail(false) else -> {} } segment.onShown(parameter, isBack); @@ -1200,7 +1192,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { } } - private fun updateSegmentPaddings() { var paddingBottom = 0f; if (fragCurrent.hasBottomBar) @@ -1211,9 +1202,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { .toInt() ); - if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) - paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP; - _fragContainerMain.setPadding( 0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics) .toInt() diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 307d1ace..23ac5eae 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -212,7 +212,7 @@ class ChannelFragment : MainFragment() { when (v) { is IPlatformVideo -> { StatePlayer.instance.clearQueue() - fragment.navigate(v).maximizeVideoDetail() + fragment.navigate(v).maximizeVideoDetail(false) } is IPlatformPlaylist -> { @@ -248,7 +248,7 @@ class ChannelFragment : MainFragment() { when (contentType) { ContentType.MEDIA -> { StatePlayer.instance.clearQueue() - fragment.navigate(url).maximizeVideoDetail() + fragment.navigate(url).maximizeVideoDetail(false) } ContentType.URL -> fragment.navigate(url) 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 04a51189..8d8f12e1 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 @@ -188,9 +188,9 @@ abstract class ContentFeedView : FeedView(content.withTimestamp(time)).maximizeVideoDetail(); + fragment.navigate(content.withTimestamp(time)).maximizeVideoDetail(false); else - fragment.navigate(content).maximizeVideoDetail(); + fragment.navigate(content).maximizeVideoDetail(false); } } else if (content is IPlatformPlaylist) { fragment.navigate(content); @@ -202,7 +202,7 @@ abstract class ContentFeedView : FeedView { StatePlayer.instance.clearQueue(); - fragment.navigate(url).maximizeVideoDetail(); + fragment.navigate(url).maximizeVideoDetail(false); }; ContentType.PLAYLIST -> fragment.navigate(url); ContentType.URL -> fragment.navigate(url); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt index 93b5e7e9..4b10adb9 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt @@ -117,7 +117,7 @@ class DownloadsFragment : MainFragment() { .asAnyWithTop(findViewById(R.id.downloads_top)) { it.onClick.subscribe { StatePlayer.instance.clearQueue(); - _frag.navigate(it).maximizeVideoDetail(); + _frag.navigate(it).maximizeVideoDetail(false); } }; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt index 2da46620..ff3e19d4 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt @@ -217,7 +217,7 @@ class HistoryFragment : MainFragment() { val diff = v.video.duration - v.position; val vid: Any = if (diff > 5) { v.video.withTimestamp(v.position) } else { v.video }; StatePlayer.instance.clearQueue(); - _fragment.navigate(vid).maximizeVideoDetail(); + _fragment.navigate(vid).maximizeVideoDetail(false); _editSearch.clearFocus(); inputMethodManager.hideSoftInputFromWindow(_editSearch.windowToken, 0); 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 5223cefc..6c4e9ee6 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 @@ -32,12 +32,11 @@ 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 com.futo.platformplayer.views.containers.CustomMotionLayout import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlin.math.min //region Fragment @UnstableApi @@ -49,8 +48,8 @@ class VideoDetailFragment() : MainFragment() { private var _isActive: Boolean = false; - private var _viewDetail : VideoDetailView? = null; - private var _view : SingleViewTouchableMotionLayout? = null; + private var _viewDetail : VideoDetailView? = null + private var _motionLayout: CustomMotionLayout? = null var isFullscreen : Boolean = false; /** @@ -59,8 +58,6 @@ class VideoDetailFragment() : MainFragment() { */ var isMinimizingFromFullScreen : Boolean = false; val onFullscreenChanged = Event1(); - var isTransitioning : Boolean = false - private set; var isInPictureInPicture : Boolean = false private set; @@ -76,13 +73,8 @@ class VideoDetailFragment() : MainFragment() { val currentUrl get() = _viewDetail?.currentUrl; val onMinimize = Event0(); - val onTransitioning = Event1(); val onMaximized = Event0(); - private var _isInitialMaximize = true; - - private val _maximizeProgress get() = _view?.progress ?: 0.0f; - private var _loadUrlOnCreate: UrlVideoWithTime? = null; private var _leavingPiP = false; @@ -263,22 +255,17 @@ class VideoDetailFragment() : MainFragment() { fun minimizeVideoDetail() { _viewDetail?.setFullscreen(false); - if(_view != null) - _view!!.transitionToStart(); + _motionLayout?.setTransition(R.id.maximize) + _motionLayout?.transitionToStart(); } - fun maximizeVideoDetail(instant: Boolean = false) { - if((_maximizeProgress > 0.9f || instant) && state != State.MAXIMIZED) { - state = State.MAXIMIZED; - onMaximized.emit(); + fun maximizeVideoDetail(instant: Boolean) { + state = State.MAXIMIZED + onMaximized.emit() + if(instant) { + _motionLayout?.setState(R.id.collapsed, _motionLayout!!.width,_motionLayout!!.height) + } else { + _motionLayout?.transitionToState(R.id.expanded) } - _view?.let { - if(!instant) { - it.transitionToEnd(); - } else { - it.progress = 1f; - onTransitioning.emit(true); - } - }; } fun closeVideoDetails() { Logger.i(TAG, "closeVideoDetails()") @@ -291,90 +278,56 @@ class VideoDetailFragment() : MainFragment() { } override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _view = inflater.inflate(R.layout.fragment_video_detail, container, false) as SingleViewTouchableMotionLayout; - _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 { - Logger.i(TAG, "onClose") - closeVideoDetails(); - }; - it.onMaximize.subscribe { maximizeVideoDetail(it) }; - it.onPlayChanged.subscribe { - if(isInPictureInPicture) { - val params = _viewDetail?.getPictureInPictureParams(); - if (params != null) - activity?.setPictureInPictureParams(params); - } - }; - it.onEnterPictureInPicture.subscribe { - Logger.i(TAG, "onEnterPictureInPicture") - isInPictureInPicture = true; - _viewDetail?.handleEnterPictureInPicture(); - _viewDetail?.invalidate(); - }; - it.onTouchCancel.subscribe { - val v = _view ?: return@subscribe; - if (v.progress >= 0.5 && v.progress < 1) { - maximizeVideoDetail(); - } - if (v.progress < 0.5 && v.progress > 0) { - minimizeVideoDetail(); - } - }; - } - _view!!.setTransitionListener(object : MotionLayout.TransitionListener { - override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) { + val viewDetail = VideoDetailView(this, inflater); + + _motionLayout = viewDetail.findViewById(R.id.videodetail_root) + viewDetail.applyFragment(this); + viewDetail.onFullscreenChanged.subscribe(::onFullscreenChanged); + viewDetail.onVideoChanged.subscribe(::onVideoChanged) + viewDetail.onMinimize.subscribe { + isMinimizingFromFullScreen = true + _motionLayout!!.setTransition(R.id.maximize) + _motionLayout!!.transitionToStart() + }; + viewDetail.onClose.subscribe { + Logger.i(TAG, "onClose") + closeVideoDetails(); + }; + viewDetail.onMaximize.subscribe { maximizeVideoDetail(it) }; + viewDetail.onPlayChanged.subscribe { + if(isInPictureInPicture) { + val params = _viewDetail?.getPictureInPictureParams(); + if (params != null) + activity?.setPictureInPictureParams(params); + } + }; + viewDetail.onEnterPictureInPicture.subscribe { + Logger.i(TAG, "onEnterPictureInPicture") + isInPictureInPicture = true; + _viewDetail?.handleEnterPictureInPicture(); + _viewDetail?.invalidate(); + }; + _motionLayout!!.addTransitionListener(object : MotionLayout.TransitionListener { + override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { _viewDetail?.stopAllGestures() - - if (state != State.MINIMIZED && progress < 0.1) { - state = State.MINIMIZED; + if (state != State.MINIMIZED && currentId == R.id.collapsed) { + state = State.MINIMIZED isMinimizingFromFullScreen = false - onMinimize.emit(); - } - else if (state != State.MAXIMIZED && progress > 0.9) { - if (_isInitialMaximize) { - state = State.CLOSED; - _isInitialMaximize = false; - } - else { - state = State.MAXIMIZED; - onMaximized.emit(); - } + onMinimize.emit() } - if (isTransitioning && (progress > 0.95 || progress < 0.05)) { - isTransitioning = false; - onTransitioning.emit(isTransitioning); - - if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p - } - else if (!isTransitioning && (progress < 0.95 && progress > 0.05)) { - isTransitioning = true; - onTransitioning.emit(isTransitioning); - - if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p + if (state != State.MAXIMIZED && currentId == R.id.expanded) { + state = State.MAXIMIZED + onMaximized.emit() } } - override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { } - override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { } - override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { } - }); - - _view?.let { - if (it.progress >= 0.5 && it.progress < 1.0) - maximizeVideoDetail(); - if (it.progress < 0.5 && it.progress > 0.0) - minimizeVideoDetail(); - } + override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {} + override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {} + override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {} + }) _loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) }; - maximizeVideoDetail(); + maximizeVideoDetail(false); SettingsActivity.settingsActivityClosed.subscribe(this) { updateOrientation() @@ -407,7 +360,8 @@ class VideoDetailFragment() : MainFragment() { } _autoRotateObserver?.startObserving() - return _view!!; + _viewDetail = viewDetail + return viewDetail } fun onUserLeaveHint() { @@ -512,21 +466,12 @@ class VideoDetailFragment() : MainFragment() { _portraitOrientationListener?.disableListener() _autoRotateObserver?.stopObserving() - _viewDetail?.let { - _viewDetail = null; - it.onDestroy(); - } - _view = null; + _viewDetail = null; } override fun onDestroy() { super.onDestroy() - _viewDetail?.let { - _viewDetail = null; - it.onDestroy(); - } - StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); Logger.i(TAG, "onDestroy"); @@ -583,13 +528,8 @@ class VideoDetailFragment() : MainFragment() { showSystemUI() } - // temporarily force the device to portrait if auto-rotate is disabled to prevent landscape when exiting full screen on a small device -// @SuppressLint("SourceLockedOrientationActivity") -// if (!isFullscreen && isSmallWindow() && !isAutoRotateEnabled() && !isMinimizingFromFullScreen) { -// activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT -// } updateOrientation(); - _view?.allowMotion = !fullscreen; + _motionLayout?.isInteractionEnabled = !fullscreen } companion object { 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 22f7ffa2..61fccbff 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 @@ -2,7 +2,6 @@ package com.futo.platformplayer.fragment.mainactivity.main 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 @@ -15,11 +14,10 @@ import android.graphics.drawable.Icon import android.net.Uri import android.support.v4.media.session.PlaybackStateCompat import android.text.Spanned -import android.util.AttributeSet import android.util.Log import android.util.Rational import android.util.TypedValue -import android.view.MotionEvent +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT @@ -30,6 +28,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.lifecycleScope import androidx.media3.common.C @@ -130,9 +129,9 @@ import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.LoaderView import com.futo.platformplayer.views.MonetizationView import com.futo.platformplayer.views.adapters.feedtypes.PreviewVideoView -import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout import com.futo.platformplayer.views.casting.CastView import com.futo.platformplayer.views.comments.AddCommentView +import com.futo.platformplayer.views.containers.CustomMotionLayout import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.overlays.DescriptionOverlay import com.futo.platformplayer.views.overlays.LiveChatOverlay @@ -160,6 +159,7 @@ import com.futo.polycentric.core.Models import com.futo.polycentric.core.Opinion import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.protobuf.ByteString +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -171,11 +171,10 @@ import kotlinx.coroutines.withContext import userpackage.Protocol import java.time.OffsetDateTime import kotlin.math.abs -import kotlin.math.max import kotlin.math.roundToLong @UnstableApi -class VideoDetailView : ConstraintLayout { +class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) : FrameLayout(inflater.context) { private val TAG = "VideoDetailView" lateinit var fragment: VideoDetailFragment; @@ -202,7 +201,7 @@ class VideoDetailView : ConstraintLayout { private val _timeBar: TimeBar; private var _upNext: UpNextView; - private val rootView: ConstraintLayout; + private val rootView: CustomMotionLayout; private val _title: TextView; private val _subTitle: TextView; @@ -231,7 +230,6 @@ class VideoDetailView : ConstraintLayout { private val _commentsList: CommentsList; - private var _minimizeProgress: Float = 0f; private val _buttonSubscribe: SubscribeButton; private val _buttonPins: RoundButtonGroup; @@ -247,7 +245,7 @@ class VideoDetailView : ConstraintLayout { private val _textResume: TextView; private val _layoutResume: LinearLayout; private var _jobHideResume: Job? = null; - private val _layoutPlayerContainer: TouchInterceptFrameLayout; + private val _layoutPlayerContainer: FrameLayout; private val _layoutChangeBottomSection: LinearLayout; //Overlays @@ -311,7 +309,6 @@ class VideoDetailView : ConstraintLayout { var allowBackground : Boolean = false private set; - val onTouchCancel = Event0(); private var _lastPositionSaveTime: Long = -1; private val DP_5 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); @@ -329,9 +326,8 @@ class VideoDetailView : ConstraintLayout { Pair(0, 10) //around live, try every 10 seconds ); - @androidx.annotation.OptIn(UnstableApi::class) - constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) { - inflate(context, R.layout.fragview_video_detail, this); + init { + inflater.inflate(R.layout.fragview_video_detail, this) //Declare Views rootView = findViewById(R.id.videodetail_root); @@ -383,8 +379,7 @@ class VideoDetailView : ConstraintLayout { _textSkip = findViewById(R.id.text_skip); _layoutResume = findViewById(R.id.layout_resume); _textResume = findViewById(R.id.text_resume); - _layoutPlayerContainer = findViewById(R.id.layout_player_container); - _layoutPlayerContainer.onClick.subscribe { onMaximize.emit(false); }; + _layoutPlayerContainer = findViewById(R.id.layout_player_container) _layoutRating = findViewById(R.id.layout_rating); _textDislikes = findViewById(R.id.text_dislikes); @@ -549,10 +544,6 @@ class VideoDetailView : ConstraintLayout { updatePlaybackTracking(position); }; - _player.onVideoClicked.subscribe { - if(_minimizeProgress < 0.5) - onMaximize.emit(false); - } _player.onSourceChanged.subscribe(::onSourceChanged); _player.onSourceEnded.subscribe { if (!fragment.isInPictureInPicture) { @@ -789,6 +780,49 @@ class VideoDetailView : ConstraintLayout { } } } + + var currentState = R.id.expanded + + rootView.addTransitionListener(object : MotionLayout.TransitionListener { + override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { + when (currentId) { + R.id.collapsed -> { + _player.gestureControl.setOnClickListener { + fragment.maximizeVideoDetail(false) + } + _player.gestureControl.controlsEnabled = false + } + + R.id.expanded -> { + _layoutResume.alpha = 1f + _cast.setButtonAlpha(1f) + _player.lockControlsAlpha(false) + _player.hideControls(false) + + _player.gestureControl.controlsEnabled = true + _player.gestureControl.setOnClickListener(null) + } + } + currentState = currentId + if(currentId == R.id.full_screen_gesture) { + setFullscreen(true) + motionLayout?.transitionToState(R.id.expanded) + motionLayout?.isInteractionEnabled = false + } + } + + override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) { + if (currentState == R.id.expanded) { + _layoutResume.alpha = 0f + _cast.setButtonAlpha(0f) + _player.lockControlsAlpha(true) + _player.hideControls(true); + } + } + + override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) { } + override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {} + }) } val _trackingUpdateTimeLock = Object(); @@ -1890,17 +1924,6 @@ class VideoDetailView : ConstraintLayout { } } - override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { - if (ev?.actionMasked == MotionEvent.ACTION_CANCEL || - ev?.actionMasked == MotionEvent.ACTION_POINTER_DOWN || - ev?.actionMasked == MotionEvent.ACTION_POINTER_UP) { - onTouchCancel.emit(); - } - - return super.onInterceptTouchEvent(ev); - } - - //Actions private fun showVideoSettings() { Logger.i(TAG, "showVideoSettings") @@ -2327,9 +2350,7 @@ class VideoDetailView : ConstraintLayout { Logger.i(TAG, "handleFullScreen(fullscreen=$fullscreen)") if(fullscreen) { - _layoutPlayerContainer.setPadding(0, 0, 0, 0); - - val lp = _container_content.layoutParams as LayoutParams; + val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams lp.topMargin = 0; _container_content.layoutParams = lp; @@ -2340,9 +2361,7 @@ class VideoDetailView : ConstraintLayout { setProgressBarOverlayed(null); } else { - _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); - - val lp = _container_content.layoutParams as LayoutParams; + val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams; lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt(); _container_content.layoutParams = lp; @@ -2558,7 +2577,7 @@ class VideoDetailView : ConstraintLayout { hideAddTo() onVideoClicked.subscribe { video, _ -> - fragment.navigate(video).maximizeVideoDetail() + fragment.navigate(video).maximizeVideoDetail(false) } onChannelClicked.subscribe { @@ -2593,17 +2612,9 @@ class VideoDetailView : ConstraintLayout { _overlay_quality_selector?.hide(); _player.fillHeight(false) - _layoutPlayerContainer.setPadding(0, 0, 0, 0); } fun handleLeavePictureInPicture() { Logger.i(TAG, "handleLeavePictureInPicture") - - if(!_player.isFullScreen) { - _player.fitHeight(); - _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); - } else { - _layoutPlayerContainer.setPadding(0, 0, 0, 0); - } } fun getPictureInPictureParams() : PictureInPictureParams { var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width ?: 0; @@ -2693,53 +2704,6 @@ class VideoDetailView : ConstraintLayout { } } - //Animation related setters - fun setMinimizeProgress(progress : Float) { - _minimizeProgress = progress; - _player.lockControlsAlpha(progress < 0.9); - _layoutPlayerContainer.shouldInterceptTouches = progress < 0.95; - - if(progress > 0.9) { - if(_minimize_controls.visibility != View.GONE) - _minimize_controls.visibility = View.GONE; - } - else if(_minimize_controls.visibility != View.VISIBLE) { - _minimize_controls.visibility = View.VISIBLE; - } - - //Switching video to fill - if(progress > 0.25) { - if(!_player.isFullScreen && _player.layoutParams.height != WRAP_CONTENT) { - _player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - if(!fragment.isInPictureInPicture) { - _player.fitHeight(); - _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); - } - else { - _layoutPlayerContainer.setPadding(0, 0, 0, 0); - } - _cast.layoutParams = _cast.layoutParams.apply { - (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, resources.displayMetrics).toInt(); - }; - setProgressBarOverlayed(false); - _player.hideControls(false); - } - } - else { - if(_player.layoutParams.height == WRAP_CONTENT) { - _player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); - _player.fillHeight(true) - _cast.layoutParams = _cast.layoutParams.apply { - (this as MarginLayoutParams).bottomMargin = 0; - }; - setProgressBarOverlayed(true); - _player.hideControls(false); - - _layoutPlayerContainer.setPadding(0, 0, 0, 0); - } - } - } - private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { _polycentricProfile = cachedPolycentricProfile; @@ -2764,42 +2728,8 @@ class VideoDetailView : ConstraintLayout { } fun setProgressBarOverlayed(isOverlayed: Boolean?) { - Logger.v(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})"); - isOverlayed?.let{ _cast.setProgressBarOverlayed(it) }; - - if(isOverlayed == null) { - //For now this seems to be the best way to keep it updated? - _playerProgress.layoutParams = _playerProgress.layoutParams.apply { - (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -12f, resources.displayMetrics).toInt(); - }; - _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); - } - else if(isOverlayed) { - _playerProgress.layoutParams = _playerProgress.layoutParams.apply { - (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -2f, resources.displayMetrics).toInt(); - }; - _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); - } - else { - _playerProgress.layoutParams = _playerProgress.layoutParams.apply { - (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics).toInt(); - }; - _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); - } - } - fun setContentAlpha(alpha: Float) { - _container_content.alpha = alpha; - } - fun setControllerAlpha(alpha: Float) { - _layoutResume.alpha = alpha; - _player.videoControls.alpha = alpha; - _cast.setButtonAlpha(alpha); - } - fun setMinimizeControlsAlpha(alpha : Float) { - _minimize_controls.alpha = alpha; - val clickable = alpha > 0.9; - if(_minimize_controls.isClickable != clickable) - _minimize_controls.isClickable = clickable; + Logger.v(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})") + isOverlayed?.let { _cast.setProgressBarOverlayed(it) } } override fun onConfigurationChanged(newConfig: Configuration?) { @@ -2811,16 +2741,6 @@ class VideoDetailView : ConstraintLayout { } } - 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 private val _taskLoadVideo = if(!isInEditMode) TaskHandler( StateApp.instance.scopeGetter, diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index 5f4d2b33..0a99868d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -106,6 +106,8 @@ class GestureControlView : LinearLayout { var fullScreenGestureEnabled = true + var controlsEnabled = true + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { LayoutInflater.from(context).inflate(R.layout.view_gesture_controls, this, true); @@ -309,6 +311,10 @@ class GestureControlView : LinearLayout { override fun onTouchEvent(event: MotionEvent?): Boolean { val ev = event ?: return super.onTouchEvent(event); + if(!controlsEnabled) { + return super.onTouchEvent(ev) + } + cancelHideJob(); if (_skipping) { diff --git a/app/src/main/java/com/futo/platformplayer/views/containers/CustomMotionLayout.kt b/app/src/main/java/com/futo/platformplayer/views/containers/CustomMotionLayout.kt new file mode 100644 index 00000000..e78e8ed1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/containers/CustomMotionLayout.kt @@ -0,0 +1,121 @@ +package com.futo.platformplayer.views.containers + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import androidx.constraintlayout.motion.widget.MotionLayout +import com.futo.platformplayer.R +import kotlin.math.abs + +class CustomMotionLayout(context: Context, attributeSet: AttributeSet? = null) : + MotionLayout(context, attributeSet) { + + private val viewToDetectTouch by lazy { + findViewById(R.id.layout_player_container) //TODO move to Attributes + } + private val viewToDetectTouch2 by lazy { + findViewById(R.id.minimize_controls) //TODO move to Attributes + } + + private var savedActionDown: MotionEvent? = null + private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop + + // intercepting touch events is necessary because something to do with PlayerControlView makes things not work + override fun onInterceptTouchEvent(event: MotionEvent?): Boolean { + val ev = event ?: return super.onInterceptTouchEvent(null) + + // special touch interception logic is unnecessary if interaction is disabled + if (!isInteractionEnabled) { + return super.onInterceptTouchEvent(ev) + } + + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + val viewRect = Rect() + viewToDetectTouch.getHitRect(viewRect) + val isInView = viewRect.contains(ev.x.toInt(), ev.y.toInt()) + viewToDetectTouch2.getHitRect(viewRect) + val isInView2 = viewRect.contains(ev.x.toInt(), ev.y.toInt()) + + // Don't intercept touches if they are outside of the player or the mini player controls + if (!isInView && !isInView2) { + return false + } + + val thing = super.onInterceptTouchEvent(ev) + // If the MotionLayout is already intercepting this touch then don't track it + if (thing) { + return true + } + + // MotionLayout didn't intercept the touch but the touch is over the player/mini controls views + // in the future the class will + // save the touch event for later + // need to replay this initial touch to the MotionLayout if it ends up turning into a drag + // return false because that matches the return from the super call above + savedActionDown?.recycle() // Recycle the old event to prevent memory leaks (if for some reason it wasn't cleaned up in the other code paths) + savedActionDown = MotionEvent.obtain(ev) + + return false + } + + MotionEvent.ACTION_MOVE -> { + val localSavedActionDown = savedActionDown + + // only handle the move event if there is a saved action stored + // then check to see if it has turned into a drag + if (localSavedActionDown != null) { + val dy = abs(ev.y - localSavedActionDown.y) + if (dy > touchSlop) { + // if it has turned into a drag then + // replay the down action saved earlier + // clean up our data + // return true so that the MotionLayout's onTouchEvent will receive future events for this gesture + // + // it is necessary to replay the down action because otherwise MotionLayout will not always initialize the drag correctly + super.onTouchEvent(localSavedActionDown) + localSavedActionDown.recycle() // Clean up the saved event after replaying + savedActionDown = null + return true + } + } + } + + // if it's an up or cancel action clean up our tracking + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + savedActionDown?.recycle() + savedActionDown = null + } + } + + // since the function hasn't handled the even this far send it to the parent class + return super.onInterceptTouchEvent(ev) + } + + // onTouchEvent is necessary to make sure that only touch and drag on the video triggers the animation (instead of everywhere on the screen) + @SuppressLint("ClickableViewAccessibility") // pretty sure this issue doesn't apply + override fun onTouchEvent(event: MotionEvent?): Boolean { + val ev = event ?: return super.onTouchEvent(null) + + // special touch event handling logic is unnecessary if interaction is disabled + if (!isInteractionEnabled) { + return super.onTouchEvent(ev) + } + + val viewRect = Rect() + viewToDetectTouch.getHitRect(viewRect) + val isInView = viewRect.contains(ev.x.toInt(), ev.y.toInt()) + viewToDetectTouch2.getHitRect(viewRect) + val isInView2 = viewRect.contains(ev.x.toInt(), ev.y.toInt()) + + // don't want to handle touches outside of the player/mini controls views + if ((!isInView && !isInView2) && event.actionMasked == MotionEvent.ACTION_DOWN) { + return false + } + return super.onTouchEvent(event) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt b/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt deleted file mode 100644 index ffc507d3..00000000 --- a/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.futo.platformplayer.views.containers - -import android.content.Context -import android.graphics.Rect -import android.util.AttributeSet -import android.view.GestureDetector -import android.view.MotionEvent -import android.view.View -import androidx.constraintlayout.motion.widget.MotionLayout -import com.futo.platformplayer.R - -class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) { - - private val viewToDetectTouch by lazy { - findViewById(R.id.touchContainer) //TODO move to Attributes - } - private val viewRect = Rect() - private var touchStarted = false - private val transitionListenerList = mutableListOf() - - var allowMotion : Boolean = true; - - init { - addTransitionListener(object : TransitionListener { - override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { - } - - override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) { - touchStarted = false - } - - override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { - } - - override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { - } - }) - - super.setTransitionListener(object : TransitionListener { - override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { - transitionListenerList.filterNotNull() - .forEach { it.onTransitionChange(p0, p1, p2, p3) } - } - - override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) { - transitionListenerList.filterNotNull() - .forEach { it.onTransitionCompleted(p0, p1) } - } - - override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { - } - - override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { - } - }) - - //isInteractionEnabled = false; - } - - override fun setTransitionListener(listener: TransitionListener?) { - addTransitionListener(listener) - } - - override fun addTransitionListener(listener: TransitionListener?) { - transitionListenerList += listener - } - - //This always triggers, workaround calling super.onTouchEvent - //Blocks click events underneath - override fun onInterceptTouchEvent(event: MotionEvent?): Boolean { - if(!allowMotion) - return false; - if(event != null) { - when (event.actionMasked) { - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - touchStarted = false - return super.onTouchEvent(event) && false; - } - } - if (!touchStarted) { - viewToDetectTouch.getHitRect(viewRect); - val isInView = viewRect.contains(event.x.toInt(), event.y.toInt()); - touchStarted = isInView - } - } - return touchStarted && super.onTouchEvent(event) && false; - } - - - //Not triggered on its own due to child views, intercept is used instead. - override fun onTouchEvent(event: MotionEvent): Boolean { - return false; - } -} \ No newline at end of file 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 ec0345fb..eedc02f3 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 @@ -139,10 +139,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { val onSourceEnded = Event0(); val onPrevious = Event0(); val onNext = Event0(); - val onChapterChanged = Event2(); - - val onVideoClicked = Event0(); val onTimeBarChanged = Event2(); @OptIn(UnstableApi::class) @@ -599,13 +596,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } if (fullScreen) { - val lp = background.layoutParams as ConstraintLayout.LayoutParams; - lp.bottomMargin = 0; - background.layoutParams = lp; + _videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.0f, Resources.getSystem().displayMetrics).toInt()) _videoView.setBackgroundColor(Color.parseColor("#FF000000")) gestureControl.hideControls(); - //videoControlsBar.visibility = View.GONE; _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; _videoControls_fullscreen.show(); @@ -613,13 +607,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase { videoControls.visibility = View.GONE; } else { - val lp = background.layoutParams as ConstraintLayout.LayoutParams; - lp.bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt(); - background.layoutParams = lp; + _videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt()) _videoView.setBackgroundColor(Color.parseColor("#00000000")) gestureControl.hideControls(); - //videoControlsBar.visibility = View.VISIBLE; _videoView.resizeMode = _desiredResizeModePortrait; videoControls.show(); @@ -650,10 +641,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _isControlsLocked = locked; } - override fun play() { - super.play(); - } - override fun onVideoSizeChanged(videoSize: VideoSize) { gestureControl.resetZoomPan() _lastSourceFit = null; @@ -768,15 +755,12 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } _videoView.resizeMode = _desiredResizeModePortrait - val marginBottom = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics) val height = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, _lastSourceFit!!, resources.displayMetrics ) - val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height + marginBottom).toInt()) - rootParams.bottomMargin = marginBottom.toInt() + val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height).toInt()) _root.layoutParams = rootParams isFitMode = true } @@ -802,11 +786,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { isFitMode = false; } - //Animated Calls - fun setEndPadding(value: Float) { - setPadding(0, 0, value.toInt(), 0) - } - fun updateRotateLock() { _control_rotate_lock.visibility = View.VISIBLE; _control_rotate_lock_fullscreen.visibility = View.VISIBLE; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1708eeb4..153ac297 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -54,7 +54,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:layout="@layout/fragview_video_detail" - android:elevation="15dp" android:visibility="invisible" /> diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index f1eeed7e..f34fc5c8 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -94,10 +94,13 @@ android:layout_width="match_parent" android:layout_height="0dp" /> + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml deleted file mode 100644 index 8eab177a..00000000 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragview_video_detail.xml b/app/src/main/res/layout/fragview_video_detail.xml index d5062c06..d6fa09d7 100644 --- a/app/src/main/res/layout/fragview_video_detail.xml +++ b/app/src/main/res/layout/fragview_video_detail.xml @@ -1,24 +1,26 @@ - + app:layoutDescription="@xml/videodetail_scene"> - + android:layout_width="0dp" + android:layout_height="0dp"> + + + + - - + + android:background="@color/black" + android:orientation="horizontal"> @@ -180,16 +174,8 @@ - + android:layout_width="0dp" + android:layout_height="0dp"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/video_player_ui.xml b/app/src/main/res/layout/video_player_ui.xml index 486936a6..a3e63e67 100644 --- a/app/src/main/res/layout/video_player_ui.xml +++ b/app/src/main/res/layout/video_player_ui.xml @@ -7,8 +7,7 @@ android:layout_height="match_parent" android:layout_gravity="bottom" android:layoutDirection="ltr" - android:orientation="vertical" - tools:targetApi="28"> + android:orientation="vertical"> @@ -10,7 +10,7 @@ + app:show_buffering="always"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - + + - + - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/layout_player_container" /> + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintHorizontal_weight="350" + app:layout_constraintStart_toEndOf="@id/layout_player_container" + app:layout_constraintTop_toTopOf="@id/layout_player_container" + app:layout_constraintWidth_max="350dp" /> + - \ No newline at end of file + + + + + + + + + + + + + + From 04c0679930a5b6b1e235baac5913926d886bf261 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 17 Dec 2024 13:44:16 -0600 Subject: [PATCH 3/4] - fix PiP issues - fix overlay issues --- .../mainactivity/main/VideoDetailFragment.kt | 9 +++--- .../mainactivity/main/VideoDetailView.kt | 1 - .../views/video/FutoVideoPlayer.kt | 14 ++++------ .../main/res/layout/fragview_video_detail.xml | 28 ++++++++++--------- app/src/main/res/xml/videodetail_scene.xml | 26 +++++++++++++++-- 5 files changed, 47 insertions(+), 31 deletions(-) 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 6c4e9ee6..122c02c8 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 @@ -255,14 +255,14 @@ class VideoDetailFragment() : MainFragment() { fun minimizeVideoDetail() { _viewDetail?.setFullscreen(false); - _motionLayout?.setTransition(R.id.maximize) - _motionLayout?.transitionToStart(); + _motionLayout?.transitionToState(R.id.collapsed) } fun maximizeVideoDetail(instant: Boolean) { state = State.MAXIMIZED onMaximized.emit() if(instant) { - _motionLayout?.setState(R.id.collapsed, _motionLayout!!.width,_motionLayout!!.height) + _motionLayout?.setTransition(R.id.maximize) + _motionLayout?.progress = 1f } else { _motionLayout?.transitionToState(R.id.expanded) } @@ -286,8 +286,7 @@ class VideoDetailFragment() : MainFragment() { viewDetail.onVideoChanged.subscribe(::onVideoChanged) viewDetail.onMinimize.subscribe { isMinimizingFromFullScreen = true - _motionLayout!!.setTransition(R.id.maximize) - _motionLayout!!.transitionToStart() + _motionLayout?.transitionToState(R.id.collapsed) }; viewDetail.onClose.subscribe { Logger.i(TAG, "onClose") 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 61fccbff..42030e63 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 @@ -807,7 +807,6 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) : if(currentId == R.id.full_screen_gesture) { setFullscreen(true) motionLayout?.transitionToState(R.id.expanded) - motionLayout?.isInteractionEnabled = false } } 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 eedc02f3..b41fae81 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 @@ -113,7 +113,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { 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; @@ -710,11 +709,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { @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 - } var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height ?: 0 @@ -760,6 +754,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _lastSourceFit!!, resources.displayMetrics ) + _videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt()) val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height).toInt()) _root.layoutParams = rootParams isFitMode = true @@ -768,9 +763,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { @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; + var layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; layoutParams.setMargins(0); _videoView.layoutParams = layoutParams; _videoView.invalidate(); @@ -781,6 +774,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase { if(isMiniPlayer){ _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM + _videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt()) + } else { + _videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.0f, Resources.getSystem().displayMetrics).toInt()) } isFitMode = false; diff --git a/app/src/main/res/layout/fragview_video_detail.xml b/app/src/main/res/layout/fragview_video_detail.xml index d6fa09d7..8f9f9866 100644 --- a/app/src/main/res/layout/fragview_video_detail.xml +++ b/app/src/main/res/layout/fragview_video_detail.xml @@ -380,19 +380,23 @@ android:id="@+id/videodetail_rating" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toStartOf="@id/buttons_pins" android:layout_marginTop="7dp" - android:layout_marginStart="15dp" /> + android:layout_marginStart="15dp" + app:layout_constraintHorizontal_chainStyle="spread_inside"/> + android:layout_height="wrap_content" + app:layout_constraintWidth_max="500dp" + app:layout_constraintHorizontal_chainStyle="spread_inside"/> @@ -591,15 +595,13 @@ + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone"/> \ No newline at end of file diff --git a/app/src/main/res/xml/videodetail_scene.xml b/app/src/main/res/xml/videodetail_scene.xml index 3fb6eb86..7a03f0f8 100644 --- a/app/src/main/res/xml/videodetail_scene.xml +++ b/app/src/main/res/xml/videodetail_scene.xml @@ -73,12 +73,18 @@ + + @@ -112,12 +118,26 @@ + + @@ -152,7 +172,7 @@ Date: Tue, 17 Dec 2024 14:46:18 -0600 Subject: [PATCH 4/4] - fix PiP when player closed - improve PiP speed on Android 12+ --- .../mainactivity/main/VideoDetailFragment.kt | 17 ++++++++--------- .../mainactivity/main/VideoDetailView.kt | 12 +++++++++--- 2 files changed, 17 insertions(+), 12 deletions(-) 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 122c02c8..785a3cad 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 @@ -269,6 +269,7 @@ class VideoDetailFragment() : MainFragment() { } fun closeVideoDetails() { Logger.i(TAG, "closeVideoDetails()") + _viewDetail?.onPlayChanged?.emit(false) state = State.CLOSED; _viewDetail?.onStop(); close(); @@ -294,12 +295,10 @@ class VideoDetailFragment() : MainFragment() { }; viewDetail.onMaximize.subscribe { maximizeVideoDetail(it) }; viewDetail.onPlayChanged.subscribe { - if(isInPictureInPicture) { - val params = _viewDetail?.getPictureInPictureParams(); - if (params != null) - activity?.setPictureInPictureParams(params); - } - }; + val params = _viewDetail?.getPictureInPictureParams(it) + if (params != null) + activity?.setPictureInPictureParams(params) + } viewDetail.onEnterPictureInPicture.subscribe { Logger.i(TAG, "onEnterPictureInPicture") isInPictureInPicture = true; @@ -367,10 +366,10 @@ class VideoDetailFragment() : MainFragment() { val viewDetail = _viewDetail; Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); - if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S && viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { _leavingPiP = false; - val params = _viewDetail?.getPictureInPictureParams(); + val params = _viewDetail?.getPictureInPictureParams(true); if(params != null) { Logger.i(TAG, "enterPictureInPictureMode") activity?.enterPictureInPictureMode(params); @@ -379,7 +378,7 @@ class VideoDetailFragment() : MainFragment() { } fun forcePictureInPicture() { - val params = _viewDetail?.getPictureInPictureParams(); + val params = _viewDetail?.getPictureInPictureParams(true); if(params != null) activity?.enterPictureInPictureMode(params); } 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 42030e63..5efe886c 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 @@ -12,6 +12,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri +import android.os.Build import android.support.v4.media.session.PlaybackStateCompat import android.text.Spanned import android.util.Log @@ -2615,7 +2616,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) : fun handleLeavePictureInPicture() { Logger.i(TAG, "handleLeavePictureInPicture") } - fun getPictureInPictureParams() : PictureInPictureParams { + fun getPictureInPictureParams(isPlaying: Boolean) : PictureInPictureParams { var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width ?: 0; var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height ?: 0; @@ -2641,11 +2642,16 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) : else RemoteAction(Icon.createWithResource(context, R.drawable.ic_play_notif), context.getString(R.string.play), context.getString(R.string.resumes_the_video), MediaControlReceiver.getPlayIntent(context, 6)); - return PictureInPictureParams.Builder() + val params = PictureInPictureParams.Builder() .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)) .setSourceRectHint(r) .setActions(listOf(playpauseAction)) - .build(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + params.setAutoEnterEnabled(isPlaying) + } + + return params.build() } //Other