Skip to content

Commit e784f8f

Browse files
committed
play video with compose
1 parent 5dda10d commit e784f8f

File tree

5 files changed

+206
-7
lines changed

5 files changed

+206
-7
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ accompanistPager = "0.36.0"
1616
lifecycleViewmodelCompose = "2.9.2"
1717
kotlinVersion = "2.2.0"
1818
roomRxjava2 = "2.7.2"
19+
materialIconsExtended = "1.6.0"
1920

2021

2122
[libraries]
@@ -43,6 +44,7 @@ accompanist-pager = {group = "com.google.accompanist",name="accompanist-pager",v
4344
accompanist-pager-indicator = {group = "com.google.accompanist",name="accompanist-pager-indicators",version.ref = "accompanistPager"}
4445
androidx-compose-runtime = {group = "androidx.compose.runtime",name="runtime-livedata",version.ref = "composeRuntimeLiveData"}
4546
androidx-lifecycle-viewmodel-compose = {group = "androidx.lifecycle",name="lifecycle-viewmodel-compose",version.ref = "lifecycleViewmodelCompose"}
47+
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "materialIconsExtended" }
4648
[plugins]
4749
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinVersion" }
4850
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinVersion" }

subs/compose/build.gradle

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
api libs.androidx.ui.graphics
4444
api libs.androidx.ui.tooling.preview
4545
api libs.androidx.material3
46+
api "androidx.compose.material:material-icons-extended-android:1.6.8"
4647
api libs.coil.compose
4748
api 'io.coil-kt:coil-gif:2.7.0'
4849

@@ -51,12 +52,6 @@ dependencies {
5152

5253
api libs.accompanist.pager
5354
api libs.accompanist.pager.indicator
54-
api libs.androidx.activity.compose
55-
api platform(libs.androidx.compose.bom)
56-
api libs.androidx.ui
57-
api libs.androidx.ui.graphics
58-
api libs.androidx.ui.tooling.preview
59-
api libs.androidx.material3
6055

6156

6257
testImplementation libs.junit
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<application>
3+
<activity android:name=".ui.VideoPlayerActivity" />
4+
</application>
5+
</manifest>

subs/compose/src/main/java/com/engineer/compose/ui/ComposeView.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ fun SelectImageButton() {
8787

8888
}
8989

90+
@Composable
91+
fun SelectVideoButton() {
92+
val context = LocalContext.current
93+
val pickVideoLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri ->
94+
if (uri != null) {
95+
val intent = Intent(context, VideoPlayerActivity::class.java)
96+
intent.putExtra("video_uri", uri.toString())
97+
context.startActivity(intent)
98+
}
99+
}
100+
Button(
101+
modifier = Modifier.padding(5.dp),
102+
onClick = { pickVideoLauncher.launch("video/*") },
103+
) {
104+
Text(text = "Pick Video")
105+
}
106+
}
107+
90108
@Composable
91109
fun MessageCard(msg: Message) {
92110
var text by rememberSaveable { mutableStateOf("") }
@@ -109,6 +127,8 @@ fun MessageCard(msg: Message) {
109127
.fillMaxSize()
110128
.horizontalScroll(rememberScrollState())
111129
) {
130+
SelectImageButton()
131+
SelectVideoButton()
112132
Button(modifier = Modifier.padding(5.dp), onClick = {
113133
Toast.makeText(context, "you clicked me", Toast.LENGTH_SHORT).show()
114134
count++
@@ -131,7 +151,6 @@ fun MessageCard(msg: Message) {
131151
}) {
132152
Text(text = "open gallery")
133153
}
134-
SelectImageButton()
135154
}
136155
Text(modifier = Modifier.padding(start = 10.dp), text = "$count")
137156
Text(modifier = Modifier.padding(start = 10.dp), text = "$list")
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package com.engineer.compose.ui
2+
3+
import android.media.MediaPlayer
4+
import android.net.Uri
5+
import android.os.Bundle
6+
import android.view.SurfaceHolder
7+
import android.view.SurfaceView
8+
import android.view.WindowManager
9+
import androidx.activity.ComponentActivity
10+
import androidx.activity.compose.setContent
11+
import androidx.compose.foundation.background
12+
import androidx.compose.foundation.layout.Arrangement
13+
import androidx.compose.foundation.layout.Box
14+
import androidx.compose.foundation.layout.Column
15+
import androidx.compose.foundation.layout.Row
16+
import androidx.compose.foundation.layout.aspectRatio
17+
import androidx.compose.foundation.layout.fillMaxSize
18+
import androidx.compose.foundation.layout.fillMaxWidth
19+
import androidx.compose.foundation.layout.padding
20+
import androidx.compose.material.icons.Icons
21+
import androidx.compose.material.icons.filled.Pause
22+
import androidx.compose.material.icons.filled.PlayArrow
23+
import androidx.compose.material3.Icon
24+
import androidx.compose.material3.IconButton
25+
import androidx.compose.material3.Slider
26+
import androidx.compose.material3.Text
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.runtime.DisposableEffect
29+
import androidx.compose.runtime.LaunchedEffect
30+
import androidx.compose.runtime.getValue
31+
import androidx.compose.runtime.mutableIntStateOf
32+
import androidx.compose.runtime.mutableStateOf
33+
import androidx.compose.runtime.remember
34+
import androidx.compose.runtime.setValue
35+
import androidx.compose.ui.Alignment
36+
import androidx.compose.ui.Modifier
37+
import androidx.compose.ui.graphics.Color
38+
import androidx.compose.ui.platform.LocalContext
39+
import androidx.compose.ui.unit.IntSize
40+
import androidx.compose.ui.unit.dp
41+
import androidx.compose.ui.viewinterop.AndroidView
42+
import androidx.core.view.WindowCompat
43+
import androidx.core.view.WindowInsetsCompat
44+
import androidx.core.view.WindowInsetsControllerCompat
45+
import com.engineer.compose.ui.ui.theme.ComposeAppTheme
46+
import kotlinx.coroutines.delay
47+
48+
class VideoPlayerActivity : ComponentActivity() {
49+
override fun onCreate(savedInstanceState: Bundle?) {
50+
super.onCreate(savedInstanceState)
51+
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
52+
windowInsetsController.systemBarsBehavior =
53+
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
54+
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
55+
56+
val videoUri = intent.getStringExtra("video_uri")
57+
setContent {
58+
ComposeAppTheme {
59+
if (videoUri != null) {
60+
VideoPlayer(Uri.parse(videoUri))
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
@Composable
68+
fun VideoPlayer(videoUri: Uri) {
69+
val context = LocalContext.current
70+
val mediaPlayer = remember { MediaPlayer() }
71+
var isPlaying by remember { mutableStateOf(false) }
72+
var videoSize by remember { mutableStateOf(IntSize.Zero) }
73+
var duration by remember { mutableIntStateOf(0) }
74+
var progress by remember { mutableIntStateOf(0) }
75+
76+
val activity = LocalContext.current as? ComponentActivity
77+
78+
LaunchedEffect(isPlaying) {
79+
while (isPlaying) {
80+
progress = mediaPlayer.currentPosition
81+
delay(1000)
82+
}
83+
}
84+
85+
Box(
86+
modifier = Modifier
87+
.fillMaxSize()
88+
.background(Color.Black),
89+
contentAlignment = Alignment.Center
90+
) {
91+
AndroidView(
92+
factory = {
93+
SurfaceView(context).apply {
94+
holder.addCallback(object : SurfaceHolder.Callback {
95+
override fun surfaceCreated(holder: SurfaceHolder) {
96+
mediaPlayer.apply {
97+
setDataSource(context, videoUri)
98+
setDisplay(holder)
99+
prepareAsync()
100+
setOnPreparedListener {
101+
videoSize = IntSize(it.videoWidth, it.videoHeight)
102+
duration = it.duration
103+
isPlaying = true
104+
it.isLooping = true
105+
it.start()
106+
}
107+
setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT)
108+
}
109+
}
110+
111+
override fun surfaceChanged(
112+
holder: SurfaceHolder,
113+
format: Int,
114+
width: Int,
115+
height: Int
116+
) {
117+
}
118+
119+
override fun surfaceDestroyed(holder: SurfaceHolder) {
120+
mediaPlayer.stop()
121+
mediaPlayer.release()
122+
}
123+
})
124+
}
125+
},
126+
modifier = Modifier
127+
.fillMaxWidth()
128+
.aspectRatio(if (videoSize.height > 0) videoSize.width.toFloat() / videoSize.height.toFloat() else 1f)
129+
)
130+
131+
Column(
132+
modifier = Modifier
133+
.align(Alignment.BottomCenter)
134+
.padding(16.dp)
135+
.fillMaxWidth()
136+
) {
137+
Row(
138+
modifier = Modifier.fillMaxWidth(),
139+
horizontalArrangement = Arrangement.SpaceBetween,
140+
verticalAlignment = Alignment.CenterVertically
141+
) {
142+
Text(text = formatTime(progress), color = Color.White)
143+
IconButton(
144+
onClick = {
145+
if (isPlaying) {
146+
mediaPlayer.pause()
147+
} else {
148+
mediaPlayer.start()
149+
}
150+
isPlaying = !isPlaying
151+
}
152+
) {
153+
Icon(
154+
imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
155+
contentDescription = if (isPlaying) "Pause" else "Play",
156+
tint = Color.White
157+
)
158+
}
159+
Text(text = formatTime(duration), color = Color.White)
160+
}
161+
}
162+
}
163+
164+
DisposableEffect(Unit) {
165+
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
166+
onDispose {
167+
mediaPlayer.release()
168+
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
169+
}
170+
}
171+
}
172+
173+
private fun formatTime(ms: Int): String {
174+
val totalSeconds = ms / 1000
175+
val minutes = totalSeconds / 60
176+
val seconds = totalSeconds % 60
177+
return String.format("%02d:%02d", minutes, seconds)
178+
}

0 commit comments

Comments
 (0)