Skip to content

Commit 3f990c3

Browse files
committed
PreviewVideoFullscreenDialog: workaround for rotated videos on sdk < 29
See comments in added code for explanation Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
1 parent bdc0fc7 commit 3f990c3

2 files changed

Lines changed: 83 additions & 18 deletions

File tree

app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
132132
FragmentPreviewMediaBinding binding;
133133
private ViewGroup emptyListView;
134134
private ExoPlayer exoPlayer;
135+
private NextcloudClient nextcloudClient;
135136

136137
/**
137138
* Creates a fragment to preview a file.
@@ -332,9 +333,9 @@ public void onStart() {
332333
final Handler handler = new Handler();
333334
Executors.newSingleThreadExecutor().execute(() -> {
334335
try {
335-
final NextcloudClient client = clientFactory.createNextcloudClient(accountManager.getUser());
336+
nextcloudClient = clientFactory.createNextcloudClient(accountManager.getUser());
336337
handler.post(() ->{
337-
exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), client);
338+
exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), nextcloudClient);
338339
exoPlayer.addListener(new ExoplayerListener(requireContext(), binding.exoplayerView, exoPlayer));
339340
playVideo();
340341
});
@@ -608,7 +609,7 @@ public boolean onTouch(View v, MotionEvent event) {
608609
private void startFullScreenVideo() {
609610
final FragmentActivity activity = getActivity();
610611
if (activity != null) {
611-
new PreviewVideoFullscreenDialog(activity, exoPlayer, binding.exoplayerView).show();
612+
new PreviewVideoFullscreenDialog(activity, nextcloudClient, exoPlayer, binding.exoplayerView).show();
612613
}
613614
}
614615

app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ package com.owncloud.android.ui.preview
2424

2525
import android.app.Activity
2626
import android.app.Dialog
27+
import android.os.Build
2728
import android.view.View
2829
import android.view.ViewGroup
2930
import android.view.Window
@@ -33,56 +34,107 @@ import androidx.core.view.WindowInsetsControllerCompat
3334
import com.google.android.exoplayer2.ExoPlayer
3435
import com.google.android.exoplayer2.Player
3536
import 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
3640
import com.owncloud.android.R
3741
import 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
*/
4752
class 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

Comments
 (0)