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:
Nik Clayton 2023-10-19 12:43:10 +02:00 committed by GitHub
parent 8a4ce7f7da
commit 3157f8d946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 30 additions and 8 deletions

View File

@ -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"
} }
} }