Przeglądaj źródła

PreviewVideoFullscreenDialog: workaround for rotated videos on sdk < 29

See comments in added code for explanation

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Álvaro Brey 2 lat temu
rodzic
commit
3f990c3f21

+ 4 - 3
app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -132,6 +132,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     FragmentPreviewMediaBinding binding;
     private ViewGroup emptyListView;
     private ExoPlayer exoPlayer;
+    private NextcloudClient nextcloudClient;
 
     /**
      * Creates a fragment to preview a file.
@@ -332,9 +333,9 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
                     final Handler handler = new Handler();
                     Executors.newSingleThreadExecutor().execute(() -> {
                         try {
-                            final NextcloudClient client = clientFactory.createNextcloudClient(accountManager.getUser());
+                            nextcloudClient = clientFactory.createNextcloudClient(accountManager.getUser());
                             handler.post(() ->{
-                                exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), client);
+                                exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), nextcloudClient);
                                 exoPlayer.addListener(new ExoplayerListener(requireContext(), binding.exoplayerView, exoPlayer));
                                 playVideo();
                             });
@@ -608,7 +609,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     private void startFullScreenVideo() {
         final FragmentActivity activity = getActivity();
         if (activity != null) {
-            new PreviewVideoFullscreenDialog(activity, exoPlayer, binding.exoplayerView).show();
+            new PreviewVideoFullscreenDialog(activity, nextcloudClient, exoPlayer, binding.exoplayerView).show();
         }
     }
 

+ 79 - 15
app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt

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