Skip to content

Commit e95f1e1

Browse files
authored
CMM-930: Improve the video thumbnails in conversations (#22340)
* Showing a video thumbnail * Playing videos * Some refactor * Removing deprecated code * detekt * Moving the video-resolver out of the VM * PR suggestions
1 parent 5db3cbf commit e95f1e1

File tree

5 files changed

+365
-32
lines changed

5 files changed

+365
-32
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package org.wordpress.android.support.he.ui
2+
3+
import android.view.ViewGroup
4+
import androidx.core.net.toUri
5+
import android.widget.FrameLayout
6+
import androidx.compose.foundation.background
7+
import androidx.compose.foundation.layout.Arrangement
8+
import androidx.compose.foundation.layout.Box
9+
import androidx.compose.foundation.layout.Column
10+
import androidx.compose.foundation.layout.Row
11+
import androidx.compose.foundation.layout.fillMaxSize
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.size
14+
import androidx.compose.foundation.shape.RoundedCornerShape
15+
import androidx.compose.material.icons.Icons
16+
import androidx.compose.material.icons.filled.Close
17+
import androidx.compose.material.icons.filled.Warning
18+
import androidx.compose.material3.Button
19+
import androidx.compose.material3.CircularProgressIndicator
20+
import androidx.compose.material3.Icon
21+
import androidx.compose.material3.IconButton
22+
import androidx.compose.material3.Text
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.DisposableEffect
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.runtime.mutableStateOf
27+
import androidx.compose.runtime.remember
28+
import androidx.compose.runtime.setValue
29+
import androidx.compose.ui.Alignment
30+
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.graphics.Color
32+
import androidx.compose.ui.platform.LocalContext
33+
import androidx.compose.ui.res.painterResource
34+
import androidx.compose.ui.res.stringResource
35+
import androidx.compose.ui.text.style.TextAlign
36+
import androidx.compose.ui.unit.dp
37+
import androidx.compose.ui.viewinterop.AndroidView
38+
import androidx.compose.ui.window.Dialog
39+
import androidx.compose.ui.window.DialogProperties
40+
import com.google.android.exoplayer2.MediaItem
41+
import com.google.android.exoplayer2.Player
42+
import com.google.android.exoplayer2.SimpleExoPlayer
43+
import com.google.android.exoplayer2.ui.PlayerView
44+
import org.wordpress.android.R
45+
import org.wordpress.android.support.he.util.VideoUrlResolver
46+
47+
@Composable
48+
fun AttachmentFullscreenVideoPlayer(
49+
videoUrl: String,
50+
onDismiss: () -> Unit,
51+
onDownload: () -> Unit = {},
52+
videoUrlResolver: VideoUrlResolver? = null
53+
) {
54+
val context = LocalContext.current
55+
var hasError by remember { mutableStateOf(false) }
56+
var resolvedUrl by remember { mutableStateOf<String?>(null) }
57+
var isResolving by remember { mutableStateOf(true) }
58+
59+
// Resolve URL redirects before playing
60+
androidx.compose.runtime.LaunchedEffect(videoUrl) {
61+
if (videoUrlResolver != null) {
62+
resolvedUrl = videoUrlResolver.resolveUrl(videoUrl)
63+
} else {
64+
resolvedUrl = videoUrl
65+
}
66+
isResolving = false
67+
}
68+
69+
val exoPlayer = remember(resolvedUrl) {
70+
// Don't create player until URL is resolved
71+
val url = resolvedUrl ?: return@remember null
72+
73+
SimpleExoPlayer.Builder(context).build().apply {
74+
// Add error listener
75+
addListener(object : Player.EventListener {
76+
override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException) {
77+
hasError = true
78+
}
79+
})
80+
81+
// Simple configuration using MediaItem
82+
val mediaItem = MediaItem.fromUri(url.toUri())
83+
setMediaItem(mediaItem)
84+
prepare()
85+
playWhenReady = true
86+
repeatMode = Player.REPEAT_MODE_OFF
87+
}
88+
}
89+
90+
DisposableEffect(Unit) {
91+
onDispose {
92+
exoPlayer?.stop()
93+
exoPlayer?.release()
94+
}
95+
}
96+
97+
Dialog(
98+
onDismissRequest = {
99+
exoPlayer?.stop()
100+
onDismiss()
101+
},
102+
properties = DialogProperties(
103+
usePlatformDefaultWidth = false,
104+
dismissOnBackPress = true,
105+
dismissOnClickOutside = false
106+
)
107+
) {
108+
Box(
109+
modifier = Modifier
110+
.fillMaxSize()
111+
.background(Color.Black)
112+
) {
113+
when {
114+
isResolving -> {
115+
// Show loading indicator while resolving URL
116+
CircularProgressIndicator(
117+
modifier = Modifier.align(Alignment.Center),
118+
color = Color.White
119+
)
120+
}
121+
hasError -> {
122+
// Show error message when video fails to load
123+
Column(
124+
modifier = Modifier
125+
.align(Alignment.Center)
126+
.padding(32.dp),
127+
horizontalAlignment = Alignment.CenterHorizontally,
128+
verticalArrangement = Arrangement.spacedBy(16.dp)
129+
) {
130+
Icon(
131+
imageVector = Icons.Default.Warning,
132+
contentDescription = null,
133+
tint = Color.White,
134+
modifier = Modifier.size(64.dp)
135+
)
136+
Text(
137+
text = stringResource(R.string.he_support_video_playback_error_title),
138+
color = Color.White,
139+
style = androidx.compose.material3.MaterialTheme.typography.titleLarge
140+
)
141+
Text(
142+
text = stringResource(R.string.he_support_video_playback_error_message),
143+
color = Color.White.copy(alpha = 0.7f),
144+
style = androidx.compose.material3.MaterialTheme.typography.bodyMedium,
145+
textAlign = TextAlign.Center
146+
)
147+
Button(
148+
onClick = {
149+
exoPlayer?.stop()
150+
onDownload()
151+
onDismiss()
152+
}
153+
) {
154+
Text(stringResource(R.string.he_support_download_video_button))
155+
}
156+
}
157+
}
158+
else -> {
159+
// Show video player when URL is resolved and no error
160+
exoPlayer?.let { player ->
161+
AndroidView(
162+
factory = { ctx ->
163+
PlayerView(ctx).apply {
164+
this.player = player
165+
useController = true
166+
layoutParams = FrameLayout.LayoutParams(
167+
ViewGroup.LayoutParams.MATCH_PARENT,
168+
ViewGroup.LayoutParams.MATCH_PARENT
169+
)
170+
}
171+
},
172+
modifier = Modifier.fillMaxSize()
173+
)
174+
}
175+
}
176+
}
177+
178+
// Top bar with close and download buttons
179+
Row(
180+
modifier = Modifier
181+
.align(Alignment.TopEnd)
182+
.padding(16.dp)
183+
.background(
184+
color = Color.Black.copy(alpha = 0.5f),
185+
shape = RoundedCornerShape(24.dp)
186+
)
187+
.padding(4.dp),
188+
horizontalArrangement = Arrangement.spacedBy(4.dp)
189+
) {
190+
// Download button
191+
IconButton(
192+
onClick = {
193+
exoPlayer?.stop()
194+
onDownload.invoke()
195+
onDismiss.invoke()
196+
}
197+
) {
198+
Icon(
199+
painter = painterResource(R.drawable.ic_get_app_white_24dp),
200+
contentDescription = stringResource(R.string.he_support_download_attachment),
201+
tint = Color.White,
202+
modifier = Modifier.size(24.dp)
203+
)
204+
}
205+
206+
// Close button
207+
IconButton(
208+
onClick = {
209+
exoPlayer?.stop()
210+
onDismiss()
211+
}
212+
) {
213+
Icon(
214+
imageVector = Icons.Filled.Close,
215+
contentDescription = stringResource(R.string.close),
216+
tint = Color.White,
217+
modifier = Modifier.size(24.dp)
218+
)
219+
}
220+
}
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)