diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt index efaf0eba..d4617fdc 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt @@ -13,7 +13,6 @@ import java.text.DecimalFormat import java.time.OffsetDateTime import java.time.temporal.ChronoUnit import kotlin.math.abs -import kotlin.time.toDuration //Long @@ -228,6 +227,14 @@ fun String.fixHtmlWhitespace(): Spanned { return Html.fromHtml(replace("\n", "
"), HtmlCompat.FROM_HTML_MODE_LEGACY); } +fun Long.formatDuration(): String { + val hours = this / 3600000 + val minutes = (this % 3600000) / 60000 + val seconds = (this % 60000) / 1000 + + return String.format("%02d:%02d:%02d", hours, minutes, seconds) +} + fun String.fixHtmlLinks(): Spanned { //TODO: Properly fix whitespace handling. val doc = Jsoup.parse(replace("\n", "
")); 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 ad606f26..5b782037 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 @@ -16,6 +16,14 @@ import android.widget.TextView import androidx.annotation.OptIn import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.setMargins +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.VideoSize +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.ui.AspectRatioFrameLayout +import androidx.media3.ui.PlayerControlView +import androidx.media3.ui.PlayerView +import androidx.media3.ui.TimeBar import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs @@ -26,18 +34,11 @@ import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event3 +import com.futo.platformplayer.formatDuration import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.behavior.GestureControlView -import androidx.media3.common.PlaybackParameters -import androidx.media3.common.VideoSize -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.ui.AspectRatioFrameLayout -import androidx.media3.ui.PlayerControlView -import androidx.media3.ui.PlayerView -import androidx.media3.ui.TimeBar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -74,7 +75,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase { private val _control_loop: ImageButton; private val _control_cast: ImageButton; private val _control_play: ImageButton; + private val _control_pause: ImageButton; private val _control_chapter: TextView; + private val _control_time: TextView; + private val _control_duration: TextView; private val _time_bar: TimeBar; private val _buttonPrevious: ImageButton; private val _buttonNext: ImageButton; @@ -91,6 +95,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase { private val _control_chapter_fullscreen: TextView; private val _buttonPrevious_fullscreen: ImageButton; private val _buttonNext_fullscreen: ImageButton; + private val _control_time_fullscreen: TextView; + private val _control_duration_fullscreen: TextView; + private val _control_pause_fullscreen: ImageButton; private val _title_fullscreen: TextView; private val _author_fullscreen: TextView; @@ -134,30 +141,36 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _videoView = findViewById(R.id.video_player); videoControls = findViewById(R.id.video_player_controller); - _control_fullscreen = videoControls.findViewById(R.id.exo_fullscreen); - _control_videosettings = videoControls.findViewById(R.id.exo_settings); - _control_minimize = videoControls.findViewById(R.id.exo_minimize); - _control_rotate_lock = videoControls.findViewById(R.id.exo_rotate_lock); - _control_loop = videoControls.findViewById(R.id.exo_loop); - _control_cast = videoControls.findViewById(R.id.exo_cast); - _control_play = videoControls.findViewById(androidx.media3.ui.R.id.exo_play); - _time_bar = videoControls.findViewById(androidx.media3.ui.R.id.exo_progress); + _control_fullscreen = videoControls.findViewById(R.id.button_fullscreen); + _control_videosettings = videoControls.findViewById(R.id.button_settings); + _control_minimize = videoControls.findViewById(R.id.button_minimize); + _control_rotate_lock = videoControls.findViewById(R.id.button_rotate_lock); + _control_loop = videoControls.findViewById(R.id.button_loop); + _control_cast = videoControls.findViewById(R.id.button_cast); + _control_play = videoControls.findViewById(R.id.button_play); + _control_pause = videoControls.findViewById(R.id.button_pause); + _time_bar = videoControls.findViewById(R.id.time_progress); _control_chapter = videoControls.findViewById(R.id.text_chapter_current); _buttonNext = videoControls.findViewById(R.id.button_next); _buttonPrevious = videoControls.findViewById(R.id.button_previous); + _control_time = videoControls.findViewById(R.id.text_position); + _control_duration = videoControls.findViewById(R.id.text_duration); _videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen); - _control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_fullscreen); - _control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_minimize); - _control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_settings); - _control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock); - _control_loop_fullscreen = videoControls.findViewById(R.id.exo_loop); - _control_cast_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_cast); - _control_play_fullscreen = videoControls.findViewById(androidx.media3.ui.R.id.exo_play); + _control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_fullscreen); + _control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_minimize); + _control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_settings); + _control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_rotate_lock); + _control_loop_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_loop); + _control_cast_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_cast); + _control_play_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_play); _control_chapter_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_chapter_current); - _time_bar_fullscreen = _videoControls_fullscreen.findViewById(androidx.media3.ui.R.id.exo_progress); + _time_bar_fullscreen = _videoControls_fullscreen.findViewById(R.id.time_progress); _buttonPrevious_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_previous); _buttonNext_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_next); + _control_time_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_position); + _control_duration_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_duration); + _control_pause_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_pause); val castVisibility = if (Settings.instance.casting.enabled) View.VISIBLE else View.GONE _control_cast.visibility = castVisibility @@ -167,11 +180,53 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _buttonNext.setOnClickListener { onNext.emit() }; _buttonPrevious_fullscreen.setOnClickListener { onPrevious.emit() }; _buttonNext_fullscreen.setOnClickListener { onNext.emit() }; + _control_play.setOnClickListener { + exoPlayer?.player?.let { + if (it.contentPosition >= it.duration) { + it.seekTo(0) + } + exoPlayer?.player?.play(); + } + updatePlayPause(); + }; + _control_play_fullscreen.setOnClickListener { + exoPlayer?.player?.let { + if (it.contentPosition >= it.duration) { + it.seekTo(0) + } + exoPlayer?.player?.play(); + } + updatePlayPause(); + }; + _control_pause.setOnClickListener { + exoPlayer?.player?.pause(); + updatePlayPause(); + }; + _control_pause_fullscreen.setOnClickListener { + exoPlayer?.player?.pause(); + updatePlayPause(); + }; + + val scrubListener = object : TimeBar.OnScrubListener { + override fun onScrubStart(timeBar: TimeBar, position: Long) { + exoPlayer?.player?.seekTo(position); + } + + override fun onScrubMove(timeBar: TimeBar, position: Long) { + exoPlayer?.player?.seekTo(position); + } + + override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { + exoPlayer?.player?.seekTo(position); + } + }; + _time_bar.addListener(scrubListener) + _time_bar_fullscreen.addListener(scrubListener) _overlay_brightness = findViewById(R.id.overlay_brightness); - _title_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_title); - _author_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_author); + _title_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_title); + _author_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_author); background = findViewById(R.id.layout_controls_background); _layoutControls = findViewById(R.id.layout_controls); @@ -303,6 +358,19 @@ class FutoVideoPlayer : FutoVideoPlayerBase { }; videoControls.setProgressUpdateListener { position, bufferedPosition -> + val currentTime = position.formatDuration() + val currentDuration = duration.formatDuration() + _control_time.text = currentTime; + _control_time_fullscreen.text = currentTime; + _control_duration.text = currentDuration; + _control_duration_fullscreen.text = currentDuration; + _time_bar_fullscreen.setDuration(duration); + _time_bar.setDuration(duration); + _time_bar_fullscreen.setPosition(position); + _time_bar.setPosition(position); + _time_bar_fullscreen.setBufferedPosition(bufferedPosition); + _time_bar.setBufferedPosition(bufferedPosition); + onTimeBarChanged.emit(position, bufferedPosition); if(!_currentChapterLoopActive) @@ -512,8 +580,24 @@ class FutoVideoPlayer : FutoVideoPlayerBase { onSourceChanged.emit(videoSource, audioSource, resume); } + private fun updatePlayPause() { + if (exoPlayer?.player?.isPlaying == true) { + _control_pause.visibility = View.VISIBLE + _control_play.visibility = View.GONE + _control_pause_fullscreen.visibility = View.VISIBLE + _control_play_fullscreen.visibility = View.GONE + } else { + _control_pause.visibility = View.GONE + _control_play.visibility = View.VISIBLE + _control_pause_fullscreen.visibility = View.GONE + _control_play_fullscreen.visibility = View.VISIBLE + } + } + override fun onPlaybackStateChanged(playbackState: Int) { Logger.v(TAG, "onPlaybackStateChanged $playbackState"); + updatePlayPause() + if (playbackState == ExoPlayer.STATE_ENDED) { if (abs(position - duration) < 2000) { onSourceEnded.emit(); diff --git a/app/src/main/res/layout/video_player_ui.xml b/app/src/main/res/layout/video_player_ui.xml index ae797f0f..cf352edd 100644 --- a/app/src/main/res/layout/video_player_ui.xml +++ b/app/src/main/res/layout/video_player_ui.xml @@ -11,7 +11,7 @@ tools:targetApi="28"> + app:layout_constraintBottom_toTopOf="@id/time_progress" /> + app:layout_constraintTop_toTopOf="@id/text_position" + app:layout_constraintBottom_toBottomOf="@id/text_position"/> - - - + app:layout_constraintBottom_toTopOf="@id/time_progress" /> + app:layout_constraintTop_toTopOf="@id/text_position" + app:layout_constraintBottom_toBottomOf="@id/text_position"/> @@ -228,7 +228,7 @@