update MotionLayout code

fix touch region bug
move more pieces to xml
upgrade full screen gesture
improve mini player UI
This commit is contained in:
Kai 2024-12-16 18:21:57 -06:00
parent f64efdc964
commit 45ded8d384
No known key found for this signature in database
20 changed files with 430 additions and 662 deletions

View file

@ -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'

View file

@ -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()

View file

@ -212,7 +212,7 @@ class ChannelFragment : MainFragment() {
when (v) {
is IPlatformVideo -> {
StatePlayer.instance.clearQueue()
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail()
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail(false)
}
is IPlatformPlaylist -> {
@ -248,7 +248,7 @@ class ChannelFragment : MainFragment() {
when (contentType) {
ContentType.MEDIA -> {
StatePlayer.instance.clearQueue()
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail()
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail(false)
}
ContentType.URL -> fragment.navigate<BrowserFragment>(url)

View file

@ -188,9 +188,9 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
StatePlayer.instance.insertToQueue(content, true);
} else {
if (Settings.instance.playback.shouldResumePreview(time))
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail();
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail(false);
else
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail();
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail(false);
}
} else if (content is IPlatformPlaylist) {
fragment.navigate<RemotePlaylistFragment>(content);
@ -202,7 +202,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
when(contentType) {
ContentType.MEDIA -> {
StatePlayer.instance.clearQueue();
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail();
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail(false);
};
ContentType.PLAYLIST -> fragment.navigate<RemotePlaylistFragment>(url);
ContentType.URL -> fragment.navigate<BrowserFragment>(url);

View file

@ -117,7 +117,7 @@ class DownloadsFragment : MainFragment() {
.asAnyWithTop(findViewById(R.id.downloads_top)) {
it.onClick.subscribe {
StatePlayer.instance.clearQueue();
_frag.navigate<VideoDetailFragment>(it).maximizeVideoDetail();
_frag.navigate<VideoDetailFragment>(it).maximizeVideoDetail(false);
}
};

View file

@ -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<VideoDetailFragment>(vid).maximizeVideoDetail();
_fragment.navigate<VideoDetailFragment>(vid).maximizeVideoDetail(false);
_editSearch.clearFocus();
inputMethodManager.hideSoftInputFromWindow(_editSearch.windowToken, 0);

View file

@ -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<Boolean>();
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<Boolean>();
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<VideoDetailView>(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 {

View file

@ -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<VideoDetailFragment>(video).maximizeVideoDetail()
fragment.navigate<VideoDetailFragment>(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<String, IPlatformVideoDetails>(
StateApp.instance.scopeGetter,

View file

@ -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) {

View file

@ -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<View>(R.id.layout_player_container) //TODO move to Attributes
}
private val viewToDetectTouch2 by lazy {
findViewById<View>(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)
}
}

View file

@ -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<View>(R.id.touchContainer) //TODO move to Attributes
}
private val viewRect = Rect()
private var touchStarted = false
private val transitionListenerList = mutableListOf<TransitionListener?>()
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;
}
}

View file

@ -139,10 +139,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
val onSourceEnded = Event0();
val onPrevious = Event0();
val onNext = Event0();
val onChapterChanged = Event2<IChapter?, Boolean>();
val onVideoClicked = Event0();
val onTimeBarChanged = Event2<Long, Long>();
@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;

View file

@ -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" />
</FrameLayout>

View file

@ -94,10 +94,13 @@
android:layout_width="match_parent"
android:layout_height="0dp" />
<!-- TODO: the padding for the recycler view needs to be the same as the minimized video player and perhaps should be dynamic based on whether the player is mini or closed-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_results"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:orientation="vertical" />
</LinearLayout>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/transparent"
app:layoutDescription="@xml/videodetail_scene"
app:layout_collapseMode="parallax">
<androidx.cardview.widget.CardView
android:id="@+id/touchContainer"
android:layout_width="match_parent"
android:layout_height="220dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="#222222" />
<com.futo.platformplayer.fragment.mainactivity.main.VideoDetailView
android:id="@+id/fragview_videodetail"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@+id/touchContainer"
app:layout_constraintEnd_toEndOf="@+id/touchContainer"
app:layout_constraintStart_toStartOf="@+id/touchContainer"
app:layout_constraintTop_toTopOf="@+id/touchContainer"
android:nestedScrollingEnabled="false" />
</com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout>

View file

@ -1,24 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.futo.platformplayer.views.containers.CustomMotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="false"
android:background="@drawable/bottom_menu_border"
android:background="@color/transparent"
android:id="@+id/videodetail_root"
android:clickable="true">
app:layoutDescription="@xml/videodetail_scene">
<com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
<FrameLayout
android:id="@+id/layout_player_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="6dp"
android:elevation="2dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
android:layout_width="0dp"
android:layout_height="0dp">
<!--this acts as a background-->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="@color/black"
android:translationY="-7dp"/>
<com.futo.platformplayer.views.video.FutoVideoPlayer
android:id="@+id/videodetail_player"
@ -38,39 +40,31 @@
android:visibility="gone"
android:elevation="4dp"
android:layout_marginBottom="6dp" />
</FrameLayout>
<androidx.media3.ui.PlayerControlView
android:id="@+id/videodetail_progress"
android:layout_width="match_parent"
android:layout_height="12dp"
android:layout_gravity="bottom"
android:layout_marginLeft="-6dp"
android:layout_marginRight="-6dp"
android:layout_marginBottom="6dp"
app:show_timeout="-1"
android:elevation="2dp"
app:controller_layout_id="@layout/video_player_ui_bar" />
</com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout>
<androidx.media3.ui.PlayerControlView
android:id="@+id/videodetail_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:show_timeout="-1"
android:elevation="1dp"
android:background="@color/black"
app:controller_layout_id="@layout/video_player_ui_bar" />
<!--Minimized Controls-->
<LinearLayout
android:id="@+id/minimize_controls"
android:orientation="horizontal"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_height="0dp"
android:paddingBottom="7dp"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:clickable="false"
android:elevation="5dp"
android:alpha="1"
app:layout_constraintTop_toTopOf="@id/layout_player_container"
app:layout_constraintBottom_toBottomOf="@id/layout_player_container"
app:layout_constraintEnd_toEndOf="@id/layout_player_container"
app:layout_constraintWidth_percent="0.7">
android:background="@color/black"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:orientation="vertical">
<!--Video Title-->
@ -180,16 +174,8 @@
<FrameLayout
android:id="@+id/contentContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="-18dp"
android:elevation="1dp"
app:layout_constraintTop_toBottomOf="@id/layout_player_container"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
android:layout_width="0dp"
android:layout_height="0dp">
<FrameLayout
android:id="@+id/videodetail_container_main"
android:layout_width="match_parent"
@ -616,4 +602,4 @@
android:layout_height="match_parent"
android:elevation="100dp"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.futo.platformplayer.views.containers.CustomMotionLayout>

View file

@ -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">
<ImageButton
android:id="@+id/button_minimize"

View file

@ -2,7 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:orientation="vertical">
@ -10,7 +10,7 @@
<com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
android:id="@+id/layout_bar"
android:layout_width="match_parent"
android:layout_height="12dp"
android:layout_height="wrap_content"
app:shouldInterceptTouches="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"

View file

@ -7,14 +7,14 @@
android:background="@color/transparent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.media3.ui.PlayerView
android:paddingBottom="7dp"
android:id="@+id/video_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:default_artwork="@drawable/placeholder_video_thumbnail"
app:use_artwork="true"
app:use_controller="false"
app:show_buffering="always"
android:layout_marginBottom="6dp" />
app:show_buffering="always"/>
<!--
<androidx.media3.ui.PlayerControlView
android:id="@+id/video_player_bar"
@ -28,8 +28,7 @@
android:id="@+id/layout_controls_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#55000000"
android:layout_marginBottom="6dp">
android:background="#55000000">
</FrameLayout>
<FrameLayout
@ -64,4 +63,4 @@
app:controller_layout_id="@layout/video_player_ui_fullscreen"
android:visibility="gone" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,208 +3,160 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/maximize"
app:constraintSetEnd="@id/expanded"
app:constraintSetStart="@id/collapsed"
app:duration="300"
app:motionInterpolator="easeInOut">
app:duration="300">
<OnSwipe
app:dragDirection="dragUp"
app:maxAcceleration="200"
app:touchAnchorId="@+id/touchContainer"
app:nestedScrollFlags="disableScroll"
app:touchAnchorId="@id/layout_player_container"
app:touchAnchorSide="top" />
<KeyFrameSet>
<!--
<KeyAttribute
android:alpha="0"
app:framePosition="0"
app:motionTarget="@id/contentContainer" />
<KeyAttribute
android:alpha="1"
app:framePosition="100"
app:motionTarget="@id/contentContainer" /> -->
<KeyAttribute
app:framePosition="3"
app:motionTarget="@id/touchContainer">
<CustomAttribute
app:attributeName="cardElevation"
app:customDimension="0dp" />
</KeyAttribute>
<!--Minimize Progress-->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="MinimizeProgress"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="MinimizeProgress"
app:customFloatValue="1" />
</KeyAttribute>
<!--Controller Alpha-->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="ControllerAlpha"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="ControllerAlpha"
app:customFloatValue="1" />
</KeyAttribute>
<!--Content Alpha-->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="ContentAlpha"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="30"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="ContentAlpha"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="ContentAlpha"
app:customFloatValue="1" />
</KeyAttribute>
<!--MinimizeControlsAlpha Alpha -->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="MinimizeControlsAlpha"
app:customFloatValue="1" />
</KeyAttribute>
<KeyAttribute
app:framePosition="20"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="MinimizeControlsAlpha"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="MinimizeControlsAlpha"
app:customFloatValue="0" />
</KeyAttribute>
<!--Padding Right-->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="VideoMinimize"
app:customFloatValue="1" />
</KeyAttribute>
<KeyAttribute
app:framePosition="20"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="VideoMinimize"
app:customFloatValue="0" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="VideoMinimize"
app:customFloatValue="0" />
</KeyAttribute>
<!--Padding Top-->
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="TopPadding"
app:customDimension="1dp" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/fragview_videodetail">
<CustomAttribute
app:attributeName="TopPadding"
app:customDimension="0dp" />
</KeyAttribute>
</KeyFrameSet>
<!--pretty sure this isn't doing anything right now-->
<OnClick
app:clickAction="transitionToEnd"
app:targetId="@id/layout_player_container" />
</Transition>
<ConstraintSet android:id="@+id/expanded">
<Transition
android:id="@+id/full_screen"
app:constraintSetEnd="@id/full_screen_gesture"
app:constraintSetStart="@id/expanded"
app:duration="300">
<Constraint
android:id="@id/touchContainer"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/fragview_videodetail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<OnSwipe
app:dragDirection="dragUp"
app:maxAcceleration="200"
app:nestedScrollFlags="disableScroll"
app:onTouchUp="autoCompleteToStart"
app:touchAnchorId="@id/layout_player_container"
app:touchAnchorSide="bottom" />
</Transition>
<ConstraintSet android:id="@+id/collapsed">
<Constraint
android:id="@id/touchContainer"
android:id="@id/layout_player_container"
android:layout_height="60dp"
android:layout_width="match_parent"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="48dp"
android:layout_marginBottom="47dp"
android:elevation="3dp"
android:paddingBottom="6dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/minimize_controls"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_weight="150"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_max="150dp" />
<Constraint
android:id="@id/contentContainer"
android:elevation="1dp"
android:orientation="vertical"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
<Constraint
android:id="@id/fragview_videodetail"
android:id="@id/minimize_controls"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_width="match_parent"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="48dp"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="1dp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
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" />
<Constraint
android:id="@id/videodetail_progress"
android:layout_height="wrap_content"
android:layout_marginTop="-11.7dp"
android:background="@color/black"
android:elevation="2dp"
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
app:layout_constraintStart_toStartOf="@id/layout_player_container"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
</ConstraintSet>
</MotionScene>
<ConstraintSet android:id="@+id/expanded">
<Constraint
android:id="@id/layout_player_container"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:elevation="2dp"
android:paddingBottom="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/contentContainer"
android:elevation="2dp"
android:orientation="vertical"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
<Constraint
android:id="@id/minimize_controls"
android:layout_width="0dp"
android:layout_height="60dp"
android:elevation="1dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/layout_player_container"
app:layout_constraintTop_toTopOf="@id/layout_player_container" />
<Constraint
android:id="@id/videodetail_progress"
android:layout_height="wrap_content"
android:layout_marginTop="-11.7dp"
android:background="@color/transparent"
android:elevation="1dp"
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
app:layout_constraintStart_toStartOf="@id/layout_player_container"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
</ConstraintSet>
<ConstraintSet android:id="@+id/full_screen_gesture">
<Constraint
android:id="@id/layout_player_container"
android:layout_height="wrap_content"
android:layout_marginTop="-130dp"
android:layout_marginBottom="0dp"
android:elevation="2dp"
android:paddingBottom="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/contentContainer"
android:elevation="1dp"
android:orientation="vertical"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
<Constraint
android:id="@id/minimize_controls"
android:layout_width="0dp"
android:layout_height="60dp"
android:elevation="1dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/layout_player_container"
app:layout_constraintTop_toTopOf="@id/layout_player_container" />
<Constraint
android:id="@id/videodetail_progress"
android:layout_height="wrap_content"
android:layout_marginTop="-11.7dp"
android:background="@color/transparent"
android:elevation="1dp"
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
app:layout_constraintStart_toStartOf="@id/layout_player_container"
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
</ConstraintSet>
</MotionScene>