fix: Adjust video playback UX behaviour (#186)
Previously, playing a video would show the controls and associated overlay for five seconds before fading them out. This obscures the video for too long. Fix this by: - Only showing the media description on start, and remove after two seconds - Show the controls (and media description) if the user taps, removing after two seconds - Pausing the video (with the pause control, or tapping on the media description) keeps the controls and description on-screen indefinitely so they are easier to read Fixes #144
This commit is contained in:
parent
8a4ce7f7da
commit
3157f8d946
|
@ -45,7 +45,6 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.exoplayer.util.EventLogger
|
import androidx.media3.exoplayer.util.EventLogger
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerControlView
|
|
||||||
import app.pachli.BuildConfig
|
import app.pachli.BuildConfig
|
||||||
import app.pachli.R
|
import app.pachli.R
|
||||||
import app.pachli.ViewMediaActivity
|
import app.pachli.ViewMediaActivity
|
||||||
|
@ -63,6 +62,17 @@ import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a video, showing media description if available.
|
||||||
|
*
|
||||||
|
* UI behaviour:
|
||||||
|
*
|
||||||
|
* - Fragment starts, media description is visible at top of screen, video starts playing
|
||||||
|
* - Media description + toolbar disappears after CONTROLS_TIMEOUT_MS
|
||||||
|
* - Tapping shows controls + media description + toolbar, which fade after CONTROLS_TIMEOUT_MS
|
||||||
|
* - Tapping pause, or the media description, pauses the video and the controls + media description
|
||||||
|
* remain visible
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ViewVideoFragment : ViewMediaFragment() {
|
class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
@ -126,6 +136,8 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.videoView.controllerShowTimeoutMs = CONTROLS_TIMEOUT_MS
|
||||||
|
|
||||||
isAudio = attachment.type == Attachment.Type.AUDIO
|
isAudio = attachment.type == Attachment.Type.AUDIO
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,7 +221,6 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
binding.progressBar.hide()
|
binding.progressBar.hide()
|
||||||
binding.videoView.useController = true
|
binding.videoView.useController = true
|
||||||
binding.videoView.showController()
|
|
||||||
}
|
}
|
||||||
else -> { /* do nothing */ }
|
else -> { /* do nothing */ }
|
||||||
}
|
}
|
||||||
|
@ -218,7 +229,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
if (isAudio) return
|
if (isAudio) return
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
hideToolbarAfterDelay()
|
||||||
} else {
|
} else {
|
||||||
handler.removeCallbacks(hideToolbar)
|
handler.removeCallbacks(hideToolbar)
|
||||||
}
|
}
|
||||||
|
@ -259,7 +270,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
if (Build.VERSION.SDK_INT <= 23 || player == null) {
|
if (Build.VERSION.SDK_INT <= 23 || player == null) {
|
||||||
initializePlayer()
|
initializePlayer()
|
||||||
if (mediaActivity.isToolbarVisible && !isAudio) {
|
if (mediaActivity.isToolbarVisible && !isAudio) {
|
||||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
hideToolbarAfterDelay()
|
||||||
}
|
}
|
||||||
binding.videoView.onResume()
|
binding.videoView.onResume()
|
||||||
}
|
}
|
||||||
|
@ -357,6 +368,15 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
binding.videoView.transitionName = attachment.url
|
binding.videoView.transitionName = attachment.url
|
||||||
|
|
||||||
|
// Clicking the description should play/pause the video
|
||||||
|
binding.mediaDescription.setOnClickListener {
|
||||||
|
if (binding.videoView.player?.isPlaying == true) {
|
||||||
|
binding.videoView.player?.pause()
|
||||||
|
} else {
|
||||||
|
binding.videoView.player?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.videoView.requestFocus()
|
binding.videoView.requestFocus()
|
||||||
|
|
||||||
if (requireArguments().getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
if (requireArguments().getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||||
|
@ -364,8 +384,8 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideToolbarAfterDelay(delayMilliseconds: Int) {
|
private fun hideToolbarAfterDelay() {
|
||||||
handler.postDelayed(hideToolbar, delayMilliseconds.toLong())
|
handler.postDelayed(hideToolbar, CONTROLS_TIMEOUT_MS.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||||
|
@ -373,6 +393,8 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view ?: return
|
||||||
|
|
||||||
isDescriptionVisible = showingDescription && visible
|
isDescriptionVisible = showingDescription && visible
|
||||||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||||
if (isDescriptionVisible) {
|
if (isDescriptionVisible) {
|
||||||
|
@ -395,7 +417,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
if (visible && (binding.videoView.player?.isPlaying == true) && !isAudio) {
|
if (visible && (binding.videoView.player?.isPlaying == true) && !isAudio) {
|
||||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
hideToolbarAfterDelay()
|
||||||
} else {
|
} else {
|
||||||
handler.removeCallbacks(hideToolbar)
|
handler.removeCallbacks(hideToolbar)
|
||||||
}
|
}
|
||||||
|
@ -405,7 +427,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ViewVideoFragment"
|
private const val TAG = "ViewVideoFragment"
|
||||||
private const val TOOLBAR_HIDE_DELAY_MS = PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS
|
private const val CONTROLS_TIMEOUT_MS = 2000 // Consistent with YouTube player
|
||||||
private const val SEEK_POSITION = "seekPosition"
|
private const val SEEK_POSITION = "seekPosition"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue