diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index b3e98243b..22d4fbb6d 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -100,6 +100,7 @@ object PreferenceKeys { const val FULLSCREEN_GESTURES = "fullscreen_gestures" const val UNLIMITED_SEARCH_HISTORY = "unlimited_search_history" const val SB_HIGHLIGHTS = "sb_highlights" + const val SHOW_TIME_LEFT = "show_time_left" /** * Background mode diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index a5eb94d06..14a8e06f3 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -799,25 +799,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { } } - @SuppressLint("SetTextI18n") - private fun refreshLiveStatus() { - // switch back to normal speed when on the end of live stream - if (exoPlayer.duration - exoPlayer.currentPosition < 7000) { - exoPlayer.setPlaybackSpeed(1F) - playerBinding.timeSeparator.visibility = View.GONE - playerBinding.liveDiff.text = "" - } else { - // live stream but not watching at the end/live position - playerBinding.timeSeparator.visibility = View.VISIBLE - val diffText = DateUtils.formatElapsedTime( - (exoPlayer.duration - exoPlayer.currentPosition) / 1000 - ) - playerBinding.liveDiff.text = "-$diffText" - } - // call the function again after 100ms - handler.postDelayed(this@PlayerFragment::refreshLiveStatus, 100) - } - /** * Seek to saved watch position if available */ private fun seekToWatchPosition() { @@ -882,16 +863,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { } } - private fun handleLiveVideo() { - playerBinding.exoPosition.visibility = View.GONE - playerBinding.liveDiff.visibility = View.VISIBLE - playerBinding.duration.text = getString(R.string.live) - playerBinding.exoTime.setOnClickListener { - exoPlayer.seekTo(exoPlayer.duration) - } - refreshLiveStatus() - } - @SuppressLint("SetTextI18n") private fun initializePlayerView() { // initialize the player view actions @@ -916,11 +887,9 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { R.string.subscribers, streams.uploaderSubscriberCount.formatShort() ) + + player.isLive = streams.livestream } - - // duration that's not greater than 0 indicates that the video is live - if (streams.livestream) handleLiveVideo() - playerBinding.exoTitle.text = streams.title // init the chapters recyclerview @@ -962,6 +931,13 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { override fun onPlaybackStateChanged(playbackState: Int) { saveWatchPosition() + // set the playback speed to one if having reached the end of a livestream + if (playbackState == Player.STATE_BUFFERING && binding.player.isLive && + exoPlayer.duration - exoPlayer.currentPosition < 700 + ) { + exoPlayer.setPlaybackSpeed(1f) + } + // check if video has ended, next video is available and autoplay is enabled. if ( playbackState == Player.STATE_ENDED && diff --git a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt index 0576daa00..b201f3c62 100644 --- a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt @@ -7,6 +7,7 @@ import android.content.res.Configuration import android.graphics.Color import android.os.Handler import android.os.Looper +import android.text.format.DateUtils import android.util.AttributeSet import android.view.MotionEvent import android.view.View @@ -17,9 +18,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.os.postDelayed import androidx.core.view.ViewCompat +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.marginStart import androidx.core.view.updateLayoutParams +import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.common.text.Cue import androidx.media3.common.util.RepeatModeUtil @@ -30,6 +33,7 @@ import androidx.media3.ui.PlayerView import androidx.media3.ui.SubtitleView import androidx.media3.ui.TimeBar import com.github.libretube.R +import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.PlayerGestureControlsViewBinding @@ -39,6 +43,7 @@ import com.github.libretube.extensions.round import com.github.libretube.helpers.AudioHelper import com.github.libretube.helpers.BrightnessHelper import com.github.libretube.helpers.PlayerHelper +import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.obj.BottomSheetItem import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.interfaces.PlayerGestureOptions @@ -72,6 +77,13 @@ open class CustomExoPlayerView( private val runnableHandler = Handler(Looper.getMainLooper()) var isPlayerLocked: Boolean = false + var isLive: Boolean = false + set(value) { + field = value + updateDisplayedDurationType() + updateCurrentPosition() + } + /** * Preferences @@ -177,6 +189,21 @@ open class CustomExoPlayerView( enqueueHideControllerTask() } }) + + // restore the duration type from the previous session + updateDisplayedDurationType() + + binding.duration.setOnClickListener { + updateDisplayedDurationType(true) + } + binding.timeLeft.setOnClickListener { + updateDisplayedDurationType(false) + } + binding.position.setOnClickListener { + if (isLive) player?.let { it.seekTo(it.duration) } + } + + updateCurrentPosition() } open fun onPlayerEvent(player: Player, playerEvents: Player.Events) = Unit @@ -191,6 +218,19 @@ open class CustomExoPlayerView( ) } + private fun updateDisplayedDurationType(showTimeLeft: Boolean? = null) { + var shouldShowTimeLeft = showTimeLeft ?: PreferenceHelper + .getBoolean(PreferenceKeys.SHOW_TIME_LEFT, false) + // always show the time left only if it's a livestream + if (isLive) shouldShowTimeLeft = true + if (showTimeLeft != null) { + // save whether to show time left or duration for next session + PreferenceHelper.putBoolean(PreferenceKeys.SHOW_TIME_LEFT, shouldShowTimeLeft) + } + binding.timeLeft.isVisible = shouldShowTimeLeft + binding.duration.isGone = shouldShowTimeLeft + } + private fun enqueueHideControllerTask() { handler.postDelayed(AUTO_HIDE_CONTROLLER_DELAY, HIDE_CONTROLLER_TOKEN) { hideController() @@ -568,6 +608,19 @@ open class CustomExoPlayerView( } } + @SuppressLint("SetTextI18n") + private fun updateCurrentPosition() { + val position = player?.currentPosition?.div(1000) ?: 0 + val duration = player?.duration?.takeIf { it != C.TIME_UNSET }?.div(1000) ?: 0 + val timeLeft = duration - position + + binding.position.text = + if (isLive) context.getString(R.string.live) else DateUtils.formatElapsedTime(position) + binding.timeLeft.text = "-${DateUtils.formatElapsedTime(timeLeft)}" + + runnableHandler.postDelayed(100, UPDATE_POSITION_TOKEN, this::updateCurrentPosition) + } + open fun getTopBarMarginDp(): Int { return if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 10 else 0 } @@ -676,6 +729,7 @@ open class CustomExoPlayerView( private const val HIDE_CONTROLLER_TOKEN = "hideController" private const val HIDE_FORWARD_BUTTON_TOKEN = "hideForwardButton" private const val HIDE_REWIND_BUTTON_TOKEN = "hideRewindButton" + private const val UPDATE_POSITION_TOKEN = "updatePosition" private const val SUBTITLE_BOTTOM_PADDING_FRACTION = 0.158f private const val ANIMATION_DURATION = 100L diff --git a/app/src/main/res/layout/exo_styled_player_control_view.xml b/app/src/main/res/layout/exo_styled_player_control_view.xml index f7cbcbce3..11876126a 100644 --- a/app/src/main/res/layout/exo_styled_player_control_view.xml +++ b/app/src/main/res/layout/exo_styled_player_control_view.xml @@ -251,19 +251,12 @@ android:layout_marginStart="10dp"> - - @@ -274,6 +267,13 @@ android:text="00:00" tools:ignore="HardcodedText" /> + +