Made the resume more persistent and not visible when loader game is visible.

This commit is contained in:
Koen J
2025-11-03 17:58:03 +01:00
parent 7d19c2357c
commit 6fbfa98ad3
3 changed files with 53 additions and 30 deletions
@@ -244,6 +244,7 @@ class VideoDetailView : ConstraintLayout {
private val _buttonSubscribe: SubscribeButton; private val _buttonSubscribe: SubscribeButton;
private val _buttonPins: RoundButtonGroup; private val _buttonPins: RoundButtonGroup;
private var _loaderGameVisible = false
//private val _buttonMore: RoundButton; //private val _buttonMore: RoundButton;
var preventPictureInPicture: Boolean = false var preventPictureInPicture: Boolean = false
@@ -261,7 +262,6 @@ class VideoDetailView : ConstraintLayout {
private val _textSkip: TextView; private val _textSkip: TextView;
private val _textResume: TextView; private val _textResume: TextView;
private val _layoutResume: LinearLayout; private val _layoutResume: LinearLayout;
private var _jobHideResume: Job? = null;
private val _layoutPlayerContainer: TouchInterceptFrameLayout; private val _layoutPlayerContainer: TouchInterceptFrameLayout;
private val _layoutChangeBottomSection: LinearLayout; private val _layoutChangeBottomSection: LinearLayout;
@@ -548,6 +548,13 @@ class VideoDetailView : ConstraintLayout {
_buttonMore = buttonMore; _buttonMore = buttonMore;
updateMoreButtons(); updateMoreButtons();
val handleLoaderGameVisibilityChanged = { b: Boolean ->
_loaderGameVisible = b
updateResumeVisibilityFor(lastPositionMilliseconds)
}
_player.loaderGameVisibilityChanged.subscribe(handleLoaderGameVisibilityChanged)
_cast.loaderGameVisibilityChanged.subscribe(handleLoaderGameVisibilityChanged)
_channelButton.setOnClickListener { _channelButton.setOnClickListener {
if (video is TutorialFragment.TutorialVideo) { if (video is TutorialFragment.TutorialVideo) {
return@setOnClickListener return@setOnClickListener
@@ -872,11 +879,6 @@ class VideoDetailView : ConstraintLayout {
_layoutResume.setOnClickListener { _layoutResume.setOnClickListener {
handleSeek(_historicalPosition * 1000); handleSeek(_historicalPosition * 1000);
val job = _jobHideResume;
_jobHideResume = null;
job?.cancel();
_layoutResume.visibility = View.GONE; _layoutResume.visibility = View.GONE;
}; };
@@ -1255,10 +1257,6 @@ class VideoDetailView : ConstraintLayout {
MediaControlReceiver.onCloseReceived.remove(this); MediaControlReceiver.onCloseReceived.remove(this);
MediaControlReceiver.onBackgroundReceived.remove(this); MediaControlReceiver.onBackgroundReceived.remove(this);
MediaControlReceiver.onSeekToReceived.remove(this); MediaControlReceiver.onSeekToReceived.remove(this);
val job = _jobHideResume;
_jobHideResume = null;
job?.cancel();
} }
//Video Setters //Video Setters
@@ -1780,26 +1778,7 @@ class VideoDetailView : ConstraintLayout {
TAG, TAG,
"Historical position: $_historicalPosition, last position: $lastPositionMilliseconds" "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"
); );
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs( updateResumeVisibilityFor(lastPositionMilliseconds)
_historicalPosition - lastPositionMilliseconds / 1000
) > 5.0
) {
_layoutResume.visibility = View.VISIBLE;
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
_jobHideResume = fragment.lifecycleScope.launch(Dispatchers.Main) {
try {
delay(8000);
_layoutResume.visibility = View.GONE;
_textResume.text = "";
} catch (e: Throwable) {
Logger.e(TAG, "Failed to set resume changes.", e);
}
}
} else {
_layoutResume.visibility = View.GONE;
_textResume.text = "";
}
} }
} }
} }
@@ -1845,6 +1824,35 @@ class VideoDetailView : ConstraintLayout {
_taskLoadRecommendations.run(videoDetail.url) _taskLoadRecommendations.run(videoDetail.url)
} }
} }
private fun shouldShowResume(positionMs: Long): Boolean {
if (_loaderGameVisible) return false
val v = video ?: return false
val resumeS = _historicalPosition
val durS = v.duration
if (_overlay_loading.visibility == View.VISIBLE) return false
if (resumeS <= 60) return false
if (durS - resumeS <= 5) return false
val posMs = positionMs
val resumeMs = resumeS * 1000
val durMs = durS * 1000L
val inFirstFewSeconds = posMs < 8_000
val notYetReachedResume = (resumeMs - posMs) > 5_000
return inFirstFewSeconds && notYetReachedResume && durMs > 0
}
private fun updateResumeVisibilityFor(positionMs: Long) {
val visible = shouldShowResume(positionMs)
if (visible) {
_layoutResume.visibility = View.VISIBLE
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}"
} else {
_layoutResume.visibility = View.GONE
_textResume.text = ""
}
}
fun loadVODChat(video: IPlatformVideoDetails) { fun loadVODChat(video: IPlatformVideoDetails) {
_liveChat?.stop(); _liveChat?.stop();
_container_content_liveChat.cancel(); _container_content_liveChat.cancel();
@@ -2805,6 +2813,8 @@ class VideoDetailView : ConstraintLayout {
_overlay_loading.visibility = View.GONE; _overlay_loading.visibility = View.GONE;
(_overlay_loading_spinner.drawable as Animatable?)?.stop() (_overlay_loading_spinner.drawable as Animatable?)?.stop()
} }
updateResumeVisibilityFor(lastPositionMilliseconds)
} }
//UI Actions //UI Actions
@@ -3095,6 +3105,8 @@ class VideoDetailView : ConstraintLayout {
handleSeek(55000); handleSeek(55000);
} }
} }
updateResumeVisibilityFor(positionMilliseconds)
} }
private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) { private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) {
@@ -68,6 +68,7 @@ class CastView : ConstraintLayout {
val onPrevious = Event0(); val onPrevious = Event0();
val onNext = Event0(); val onNext = Event0();
val onTimeJobTimeChanged_s = Event1<Long>() val onTimeJobTimeChanged_s = Event1<Long>()
val loaderGameVisibilityChanged = Event1<Boolean>();
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
@@ -90,6 +91,7 @@ class CastView : ConstraintLayout {
_gestureControlView = findViewById(R.id.gesture_control); _gestureControlView = findViewById(R.id.gesture_control);
_loaderGame = findViewById(R.id.loader_overlay) _loaderGame = findViewById(R.id.loader_overlay)
_loaderGame.visibility = View.GONE _loaderGame.visibility = View.GONE
loaderGameVisibilityChanged.emit(false)
_gestureControlView.fullScreenGestureEnabled = false _gestureControlView.fullScreenGestureEnabled = false
_gestureControlView.setupTouchArea(); _gestureControlView.setupTouchArea();
@@ -319,15 +321,18 @@ class CastView : ConstraintLayout {
if (isLoading) { if (isLoading) {
_loaderGame.visibility = View.VISIBLE _loaderGame.visibility = View.VISIBLE
_loaderGame.startLoader() _loaderGame.startLoader()
loaderGameVisibilityChanged.emit(true)
} else { } else {
_loaderGame.visibility = View.GONE _loaderGame.visibility = View.GONE
_loaderGame.stopAndResetLoader() _loaderGame.stopAndResetLoader()
loaderGameVisibilityChanged.emit(false)
} }
} }
fun setLoading(expectedDurationMs: Int) { fun setLoading(expectedDurationMs: Int) {
_loaderGame.visibility = View.VISIBLE _loaderGame.visibility = View.VISIBLE
_loaderGame.startLoader(expectedDurationMs.toLong()) _loaderGame.startLoader(expectedDurationMs.toLong())
loaderGameVisibilityChanged.emit(true)
} }
companion object { companion object {
@@ -164,6 +164,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private val _loaderGame: TargetTapLoaderView private val _loaderGame: TargetTapLoaderView
val loaderGameVisibilityChanged = Event1<Boolean>();
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) { constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
LayoutInflater.from(context).inflate(R.layout.video_view, this, true); LayoutInflater.from(context).inflate(R.layout.video_view, this, true);
@@ -206,6 +208,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
_loaderGame = findViewById(R.id.loader_overlay) _loaderGame = findViewById(R.id.loader_overlay)
_loaderGame.visibility = View.GONE _loaderGame.visibility = View.GONE
loaderGameVisibilityChanged.emit(false)
_control_chapter.setOnClickListener { _control_chapter.setOnClickListener {
_currentChapter?.let { _currentChapter?.let {
@@ -894,15 +897,18 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
if (isLoading) { if (isLoading) {
_loaderGame.visibility = View.VISIBLE _loaderGame.visibility = View.VISIBLE
_loaderGame.startLoader() _loaderGame.startLoader()
loaderGameVisibilityChanged.emit(true)
} else { } else {
_loaderGame.visibility = View.GONE _loaderGame.visibility = View.GONE
_loaderGame.stopAndResetLoader() _loaderGame.stopAndResetLoader()
loaderGameVisibilityChanged.emit(false)
} }
} }
override fun setLoading(expectedDurationMs: Int) { override fun setLoading(expectedDurationMs: Int) {
_loaderGame.visibility = View.VISIBLE _loaderGame.visibility = View.VISIBLE
_loaderGame.startLoader(expectedDurationMs.toLong()) _loaderGame.startLoader(expectedDurationMs.toLong())
loaderGameVisibilityChanged.emit(true)
} }
override fun switchToVideoMode() { override fun switchToVideoMode() {