@@ -24,6 +24,7 @@ package com.owncloud.android.ui.preview
2424
2525import android.app.Activity
2626import android.app.Dialog
27+ import android.os.Build
2728import android.view.View
2829import android.view.ViewGroup
2930import android.view.Window
@@ -33,56 +34,107 @@ import androidx.core.view.WindowInsetsControllerCompat
3334import com.google.android.exoplayer2.ExoPlayer
3435import com.google.android.exoplayer2.Player
3536import com.google.android.exoplayer2.ui.StyledPlayerView
37+ import com.nextcloud.client.media.ExoplayerListener
38+ import com.nextcloud.client.media.NextcloudExoPlayer
39+ import com.nextcloud.common.NextcloudClient
3640import com.owncloud.android.R
3741import com.owncloud.android.databinding.DialogPreviewVideoBinding
42+ import com.owncloud.android.lib.common.utils.Log_OC
3843
3944/* *
4045 * Transfers a previously playing video to a fullscreen dialog, and handles the switch back to the previous player
4146 * when closed
4247 *
4348 * @param activity the Activity hosting the original non-fullscreen player
44- * @param exoPlayer the ExoPlayer playing the video
45- * @param sourceView the original non-fullscreen surface that [exoPlayer ] is linked to
49+ * @param sourceExoPlayer the ExoPlayer playing the video
50+ * @param sourceView the original non-fullscreen surface that [sourceExoPlayer ] is linked to
4651 */
4752class PreviewVideoFullscreenDialog (
4853 private val activity : Activity ,
49- private val exoPlayer : ExoPlayer ,
54+ nextcloudClient : NextcloudClient ,
55+ private val sourceExoPlayer : ExoPlayer ,
5056 private val sourceView : StyledPlayerView
5157) : Dialog(sourceView.context, android.R .style.Theme_Black_NoTitleBar_Fullscreen ) {
5258
5359 private val binding: DialogPreviewVideoBinding = DialogPreviewVideoBinding .inflate(layoutInflater)
5460 private var playingStateListener: Player .Listener ? = null
5561
62+ /* *
63+ * exoPlayer instance used for this view, either the original one or a new one in specific cases.
64+ * @see getShouldUseRotatedVideoWorkaround
65+ */
66+ private val mExoPlayer: ExoPlayer
67+
68+ /* *
69+ * Videos with rotation metadata present a bug in sdk < 30 where they are rotated incorrectly and stretched when
70+ * the video is resumed on a new surface. To work around this, in those circumstances we'll create a new ExoPlayer
71+ * instance, which is slower but should avoid the bug.
72+ */
73+ private val shouldUseRotatedVideoWorkaround
74+ get() = Build .VERSION .SDK_INT < Build .VERSION_CODES .R && isRotatedVideo()
75+
5676 init {
5777 addContentView(
5878 binding.root,
5979 ViewGroup .LayoutParams (ViewGroup .LayoutParams .MATCH_PARENT , ViewGroup .LayoutParams .MATCH_PARENT )
6080 )
81+ mExoPlayer = getExoPlayer(nextcloudClient)
82+ if (shouldUseRotatedVideoWorkaround) {
83+ sourceExoPlayer.currentMediaItem?.let { mExoPlayer.setMediaItem(it, sourceExoPlayer.currentPosition) }
84+ binding.videoPlayer.player = mExoPlayer
85+ mExoPlayer.prepare()
86+ }
87+ }
88+
89+ private fun isRotatedVideo (): Boolean {
90+ val videoFormat = sourceExoPlayer.videoFormat
91+ return videoFormat != null && videoFormat.rotationDegrees != 0
92+ }
93+
94+ private fun getExoPlayer (nextcloudClient : NextcloudClient ): ExoPlayer {
95+ return if (shouldUseRotatedVideoWorkaround) {
96+ Log_OC .d(TAG , " Using new ExoPlayer instance to deal with rotated video" )
97+ NextcloudExoPlayer
98+ .createNextcloudExoplayer(sourceView.context, nextcloudClient)
99+ .apply {
100+ addListener(ExoplayerListener (sourceView.context, binding.videoPlayer, this ))
101+ }
102+ } else {
103+ sourceExoPlayer
104+ }
61105 }
62106
63107 override fun show () {
64- val isPlaying = exoPlayer .isPlaying
108+ val isPlaying = sourceExoPlayer .isPlaying
65109 if (isPlaying) {
66- exoPlayer .pause()
110+ sourceExoPlayer .pause()
67111 }
68112 enableImmersiveMode()
69113 setOnShowListener {
70- StyledPlayerView .switchTargetView(exoPlayer, sourceView, binding.videoPlayer )
114+ switchTargetViewFromSource( )
71115 setListeners()
72116 if (isPlaying) {
73- exoPlayer .play()
117+ mExoPlayer .play()
74118 }
75119 binding.videoPlayer.showController()
76120 }
77121 super .show()
78122 }
79123
124+ private fun switchTargetViewFromSource () {
125+ if (shouldUseRotatedVideoWorkaround) {
126+ mExoPlayer.seekTo(sourceExoPlayer.currentPosition)
127+ } else {
128+ StyledPlayerView .switchTargetView(sourceExoPlayer, sourceView, binding.videoPlayer)
129+ }
130+ }
131+
80132 private fun setListeners () {
81133 binding.root.findViewById<View >(R .id.exo_exit_fs).setOnClickListener { onBackPressed() }
82134 val pauseButton: View = binding.root.findViewById(R .id.exo_pause)
83- pauseButton.setOnClickListener { exoPlayer .pause() }
135+ pauseButton.setOnClickListener { sourceExoPlayer .pause() }
84136 val playButton: View = binding.root.findViewById(R .id.exo_play)
85- playButton.setOnClickListener { exoPlayer .play() }
137+ playButton.setOnClickListener { sourceExoPlayer .play() }
86138
87139 val playListener = object : Player .Listener {
88140 override fun onIsPlayingChanged (isPlaying : Boolean ) {
@@ -96,29 +148,37 @@ class PreviewVideoFullscreenDialog(
96148 }
97149 }
98150 }
99- exoPlayer .addListener(playListener)
151+ mExoPlayer .addListener(playListener)
100152 playingStateListener = playListener
101153 }
102154
103155 override fun onBackPressed () {
104- val isPlaying = exoPlayer .isPlaying
156+ val isPlaying = mExoPlayer .isPlaying
105157 if (isPlaying) {
106- exoPlayer .pause()
158+ mExoPlayer .pause()
107159 }
108160 disableImmersiveMode()
109161 setOnDismissListener {
110162 playingStateListener?.let {
111- exoPlayer .removeListener(it)
163+ mExoPlayer .removeListener(it)
112164 }
113- StyledPlayerView .switchTargetView(exoPlayer, binding.videoPlayer, sourceView )
165+ switchTargetViewToSource( )
114166 if (isPlaying) {
115- exoPlayer .play()
167+ sourceExoPlayer .play()
116168 }
117169 sourceView.showController()
118170 }
119171 dismiss()
120172 }
121173
174+ private fun switchTargetViewToSource () {
175+ if (shouldUseRotatedVideoWorkaround) {
176+ sourceExoPlayer.seekTo(mExoPlayer.currentPosition)
177+ } else {
178+ StyledPlayerView .switchTargetView(sourceExoPlayer, binding.videoPlayer, sourceView)
179+ }
180+ }
181+
122182 private fun enableImmersiveMode () {
123183 // for immersive mode to work properly, need to disable statusbar on activity window, but nav bar in dialog
124184 // otherwise dialog navbar is not hidden, or statusbar padding is the wrong color
@@ -145,4 +205,8 @@ class PreviewVideoFullscreenDialog(
145205 windowInsetsController.show(WindowInsetsCompat .Type .systemBars())
146206 } ? : return
147207 }
208+
209+ companion object {
210+ private val TAG = PreviewVideoFullscreenDialog ::class .simpleName
211+ }
148212}
0 commit comments