|
@@ -24,6 +24,7 @@ package com.owncloud.android.ui.preview
|
|
|
|
|
|
import android.app.Activity
|
|
|
import android.app.Dialog
|
|
|
+import android.os.Build
|
|
|
import android.view.View
|
|
|
import android.view.ViewGroup
|
|
|
import android.view.Window
|
|
@@ -33,56 +34,107 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|
|
import com.google.android.exoplayer2.ExoPlayer
|
|
|
import com.google.android.exoplayer2.Player
|
|
|
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
|
+import com.nextcloud.client.media.ExoplayerListener
|
|
|
+import com.nextcloud.client.media.NextcloudExoPlayer
|
|
|
+import com.nextcloud.common.NextcloudClient
|
|
|
import com.owncloud.android.R
|
|
|
import com.owncloud.android.databinding.DialogPreviewVideoBinding
|
|
|
+import com.owncloud.android.lib.common.utils.Log_OC
|
|
|
|
|
|
/**
|
|
|
* Transfers a previously playing video to a fullscreen dialog, and handles the switch back to the previous player
|
|
|
* when closed
|
|
|
*
|
|
|
* @param activity the Activity hosting the original non-fullscreen player
|
|
|
- * @param exoPlayer the ExoPlayer playing the video
|
|
|
- * @param sourceView the original non-fullscreen surface that [exoPlayer] is linked to
|
|
|
+ * @param sourceExoPlayer the ExoPlayer playing the video
|
|
|
+ * @param sourceView the original non-fullscreen surface that [sourceExoPlayer] is linked to
|
|
|
*/
|
|
|
class PreviewVideoFullscreenDialog(
|
|
|
private val activity: Activity,
|
|
|
- private val exoPlayer: ExoPlayer,
|
|
|
+ nextcloudClient: NextcloudClient,
|
|
|
+ private val sourceExoPlayer: ExoPlayer,
|
|
|
private val sourceView: StyledPlayerView
|
|
|
) : Dialog(sourceView.context, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
|
|
|
|
|
|
private val binding: DialogPreviewVideoBinding = DialogPreviewVideoBinding.inflate(layoutInflater)
|
|
|
private var playingStateListener: Player.Listener? = null
|
|
|
|
|
|
+ /**
|
|
|
+ * exoPlayer instance used for this view, either the original one or a new one in specific cases.
|
|
|
+ * @see getShouldUseRotatedVideoWorkaround
|
|
|
+ */
|
|
|
+ private val mExoPlayer: ExoPlayer
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Videos with rotation metadata present a bug in sdk < 30 where they are rotated incorrectly and stretched when
|
|
|
+ * the video is resumed on a new surface. To work around this, in those circumstances we'll create a new ExoPlayer
|
|
|
+ * instance, which is slower but should avoid the bug.
|
|
|
+ */
|
|
|
+ private val shouldUseRotatedVideoWorkaround
|
|
|
+ get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R && isRotatedVideo()
|
|
|
+
|
|
|
init {
|
|
|
addContentView(
|
|
|
binding.root,
|
|
|
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
|
)
|
|
|
+ mExoPlayer = getExoPlayer(nextcloudClient)
|
|
|
+ if (shouldUseRotatedVideoWorkaround) {
|
|
|
+ sourceExoPlayer.currentMediaItem?.let { mExoPlayer.setMediaItem(it, sourceExoPlayer.currentPosition) }
|
|
|
+ binding.videoPlayer.player = mExoPlayer
|
|
|
+ mExoPlayer.prepare()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun isRotatedVideo(): Boolean {
|
|
|
+ val videoFormat = sourceExoPlayer.videoFormat
|
|
|
+ return videoFormat != null && videoFormat.rotationDegrees != 0
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun getExoPlayer(nextcloudClient: NextcloudClient): ExoPlayer {
|
|
|
+ return if (shouldUseRotatedVideoWorkaround) {
|
|
|
+ Log_OC.d(TAG, "Using new ExoPlayer instance to deal with rotated video")
|
|
|
+ NextcloudExoPlayer
|
|
|
+ .createNextcloudExoplayer(sourceView.context, nextcloudClient)
|
|
|
+ .apply {
|
|
|
+ addListener(ExoplayerListener(sourceView.context, binding.videoPlayer, this))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sourceExoPlayer
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override fun show() {
|
|
|
- val isPlaying = exoPlayer.isPlaying
|
|
|
+ val isPlaying = sourceExoPlayer.isPlaying
|
|
|
if (isPlaying) {
|
|
|
- exoPlayer.pause()
|
|
|
+ sourceExoPlayer.pause()
|
|
|
}
|
|
|
enableImmersiveMode()
|
|
|
setOnShowListener {
|
|
|
- StyledPlayerView.switchTargetView(exoPlayer, sourceView, binding.videoPlayer)
|
|
|
+ switchTargetViewFromSource()
|
|
|
setListeners()
|
|
|
if (isPlaying) {
|
|
|
- exoPlayer.play()
|
|
|
+ mExoPlayer.play()
|
|
|
}
|
|
|
binding.videoPlayer.showController()
|
|
|
}
|
|
|
super.show()
|
|
|
}
|
|
|
|
|
|
+ private fun switchTargetViewFromSource() {
|
|
|
+ if (shouldUseRotatedVideoWorkaround) {
|
|
|
+ mExoPlayer.seekTo(sourceExoPlayer.currentPosition)
|
|
|
+ } else {
|
|
|
+ StyledPlayerView.switchTargetView(sourceExoPlayer, sourceView, binding.videoPlayer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private fun setListeners() {
|
|
|
binding.root.findViewById<View>(R.id.exo_exit_fs).setOnClickListener { onBackPressed() }
|
|
|
val pauseButton: View = binding.root.findViewById(R.id.exo_pause)
|
|
|
- pauseButton.setOnClickListener { exoPlayer.pause() }
|
|
|
+ pauseButton.setOnClickListener { sourceExoPlayer.pause() }
|
|
|
val playButton: View = binding.root.findViewById(R.id.exo_play)
|
|
|
- playButton.setOnClickListener { exoPlayer.play() }
|
|
|
+ playButton.setOnClickListener { sourceExoPlayer.play() }
|
|
|
|
|
|
val playListener = object : Player.Listener {
|
|
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
@@ -96,29 +148,37 @@ class PreviewVideoFullscreenDialog(
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- exoPlayer.addListener(playListener)
|
|
|
+ mExoPlayer.addListener(playListener)
|
|
|
playingStateListener = playListener
|
|
|
}
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
- val isPlaying = exoPlayer.isPlaying
|
|
|
+ val isPlaying = mExoPlayer.isPlaying
|
|
|
if (isPlaying) {
|
|
|
- exoPlayer.pause()
|
|
|
+ mExoPlayer.pause()
|
|
|
}
|
|
|
disableImmersiveMode()
|
|
|
setOnDismissListener {
|
|
|
playingStateListener?.let {
|
|
|
- exoPlayer.removeListener(it)
|
|
|
+ mExoPlayer.removeListener(it)
|
|
|
}
|
|
|
- StyledPlayerView.switchTargetView(exoPlayer, binding.videoPlayer, sourceView)
|
|
|
+ switchTargetViewToSource()
|
|
|
if (isPlaying) {
|
|
|
- exoPlayer.play()
|
|
|
+ sourceExoPlayer.play()
|
|
|
}
|
|
|
sourceView.showController()
|
|
|
}
|
|
|
dismiss()
|
|
|
}
|
|
|
|
|
|
+ private fun switchTargetViewToSource() {
|
|
|
+ if (shouldUseRotatedVideoWorkaround) {
|
|
|
+ sourceExoPlayer.seekTo(mExoPlayer.currentPosition)
|
|
|
+ } else {
|
|
|
+ StyledPlayerView.switchTargetView(sourceExoPlayer, binding.videoPlayer, sourceView)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private fun enableImmersiveMode() {
|
|
|
// for immersive mode to work properly, need to disable statusbar on activity window, but nav bar in dialog
|
|
|
// otherwise dialog navbar is not hidden, or statusbar padding is the wrong color
|
|
@@ -145,4 +205,8 @@ class PreviewVideoFullscreenDialog(
|
|
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
|
|
} ?: return
|
|
|
}
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ private val TAG = PreviewVideoFullscreenDialog::class.simpleName
|
|
|
+ }
|
|
|
}
|