Small changes to the media player (#1572)

* Resolving some issues with media playback.

* Changing ViewVideoFragment so that it fully handles autohiding the
  media controller, allowing consistency between it and the toolbar

* Fixed an issue where the toolbar and description were not fading in

* Do not hide the toolbar/media player if the video is loading or paused

* Created "ExposedPlayPausedVideoView" to allow hooking play/pause

* Fix back button when viewing videos

* Back button did not work if video controls were visible.

* Tweak audio player

* Always show the controls when audio begins playing

* Do not auto-hide the controls if the player is playing audio

* Address PR comments

* Use overloaded constructor
* Remove m prefix

* Fix subtle media player issues

* Fix audio player incorrectly auto-hiding after hiding/showing toolbar

* Only subscribe touch listener once content is ready
  - Prevents top toolbar visibility from getting out of phase with audio player visibility if hidden during load
This commit is contained in:
Vivianne 2020-01-16 10:01:02 -08:00 committed by Konrad Pozniak
parent 21a7201f8a
commit a7c1345085
4 changed files with 93 additions and 8 deletions

View File

@ -186,8 +186,14 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
for (listener in toolbarVisibilityListeners) { for (listener in toolbarVisibilityListeners) {
listener(isToolbarVisible) listener(isToolbarVisible)
} }
val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE
val alpha = if (isToolbarVisible) 1.0f else 0.0f val alpha = if (isToolbarVisible) 1.0f else 0.0f
if (isToolbarVisible) {
// If to be visible, need to make visible immediately and animate alpha
toolbar.alpha = 0.0f
toolbar.visibility = visibility
}
toolbar.animate().alpha(alpha) toolbar.animate().alpha(alpha)
.setListener(object : AnimatorListenerAdapter() { .setListener(object : AnimatorListenerAdapter() {

View File

@ -21,6 +21,7 @@ import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -31,6 +32,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
import kotlinx.android.synthetic.main.activity_view_media.* import kotlinx.android.synthetic.main.activity_view_media.*
import kotlinx.android.synthetic.main.fragment_view_video.* import kotlinx.android.synthetic.main.fragment_view_video.*
@ -41,11 +43,13 @@ class ViewVideoFragment : ViewMediaFragment() {
// Hoist toolbar hiding to activity so it can track state across different fragments // Hoist toolbar hiding to activity so it can track state across different fragments
// This is explicitly stored as runnable so that we pass it to the handler later for cancellation // This is explicitly stored as runnable so that we pass it to the handler later for cancellation
mediaActivity.onPhotoTap() mediaActivity.onPhotoTap()
mediaController.hide()
} }
private lateinit var mediaActivity: ViewMediaActivity private lateinit var mediaActivity: ViewMediaActivity
private val TOOLBAR_HIDE_DELAY_MS = 3000L private val TOOLBAR_HIDE_DELAY_MS = 3000L
override lateinit var descriptionView : TextView override lateinit var descriptionView : TextView
private lateinit var mediaController : MediaController private lateinit var mediaController : MediaController
private var isAudio = false
override fun setUserVisibleHint(isVisibleToUser: Boolean) { override fun setUserVisibleHint(isVisibleToUser: Boolean) {
// Start/pause/resume video playback as fragment is shown/hidden // Start/pause/resume video playback as fragment is shown/hidden
@ -72,14 +76,43 @@ class ViewVideoFragment : ViewMediaFragment() {
videoView.transitionName = url videoView.transitionName = url
videoView.setVideoPath(url) videoView.setVideoPath(url)
mediaController = MediaController(mediaActivity) mediaController = object : MediaController(mediaActivity) {
override fun show(timeout: Int) {
// We're doing manual auto-close management.
// Also, take focus back from the pause button so we can use the back button.
super.show(0)
mediaController.requestFocus()
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
if (event?.keyCode == KeyEvent.KEYCODE_BACK) {
if (event.action == KeyEvent.ACTION_UP) {
hide()
activity?.supportFinishAfterTransition()
}
return true
}
return super.dispatchKeyEvent(event)
}
}
mediaController.setMediaPlayer(videoView) mediaController.setMediaPlayer(videoView)
videoView.setMediaController(mediaController) videoView.setMediaController(mediaController)
videoView.requestFocus() videoView.requestFocus()
videoView.setOnTouchListener { _, _ -> videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
mediaActivity.onPhotoTap() override fun onPause() {
false handler.removeCallbacks(hideToolbar)
} }
override fun onPlay() {
// Audio doesn't cause the controller to show automatically,
// and we only want to hide the toolbar if it's a video.
if (isAudio) {
mediaController.show()
} else {
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
}
}
})
videoView.setOnPreparedListener { mp -> videoView.setOnPreparedListener { mp ->
val containerWidth = videoContainer.measuredWidth.toFloat() val containerWidth = videoContainer.measuredWidth.toFloat()
val containerHeight = videoContainer.measuredHeight.toFloat() val containerHeight = videoContainer.measuredHeight.toFloat()
@ -94,10 +127,16 @@ class ViewVideoFragment : ViewMediaFragment() {
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
} }
// Wait until the media is loaded before accepting taps as we don't want toolbar to
// be hidden until then.
videoView.setOnTouchListener { _, _ ->
mediaActivity.onPhotoTap()
false
}
progressBar.hide() progressBar.hide()
mp.isLooping = true mp.isLooping = true
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) { if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
videoView.start() videoView.start()
} }
} }
@ -126,6 +165,7 @@ class ViewVideoFragment : ViewMediaFragment() {
throw IllegalArgumentException("attachment has to be set") throw IllegalArgumentException("attachment has to be set")
} }
url = attachment.url url = attachment.url
isAudio = attachment.type == Attachment.Type.AUDIO
finalizeViewSetup(url, attachment.previewUrl, attachment.description) finalizeViewSetup(url, attachment.previewUrl, attachment.description)
} }
@ -136,6 +176,12 @@ class ViewVideoFragment : ViewMediaFragment() {
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 to be visible, need to make visible immediately and animate alpha
descriptionView.alpha = 0.0f
descriptionView.visible(isDescriptionVisible)
}
descriptionView.animate().alpha(alpha) descriptionView.animate().alpha(alpha)
.setListener(object : AnimatorListenerAdapter() { .setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
@ -145,7 +191,7 @@ class ViewVideoFragment : ViewMediaFragment() {
}) })
.start() .start()
if (visible) { if (visible && videoView.isPlaying && !isAudio) {
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS) hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
} else { } else {
handler.removeCallbacks(hideToolbar) handler.removeCallbacks(hideToolbar)

View File

@ -0,0 +1,33 @@
package com.keylesspalace.tusky.view
import android.content.Context
import android.util.AttributeSet
import android.widget.VideoView
class ExposedPlayPauseVideoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: VideoView(context, attrs, defStyleAttr) {
private var listener: PlayPauseListener? = null
fun setPlayPauseListener(listener: PlayPauseListener) {
this.listener = listener
}
override fun start() {
super.start()
listener?.onPlay()
}
override fun pause() {
super.pause()
listener?.onPause()
}
interface PlayPauseListener {
fun onPlay()
fun onPause()
}
}

View File

@ -23,7 +23,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Some media description" /> tools:text="Some media description" />
<VideoView <com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
android:id="@+id/videoView" android:id="@+id/videoView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"