Skip to content

Commit d62878e

Browse files
committed
refactor: inject Exopalyer with hilt on viewModel
1 parent 53f400f commit d62878e

File tree

6 files changed

+162
-16
lines changed

6 files changed

+162
-16
lines changed

app/src/main/java/com/paradoxo/materialgram/data/VideoLocalDataSource.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class VideoLocalDataSource {
1010
"https://raw.githubusercontent.com/git-jr/sample-files/main/images/android%20invaders.png"
1111
),
1212
Video(
13-
"https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4",
13+
"https://github.com/K6pkus/sample-api/raw/main/lv_0_20230929192816.mp4",
1414
"Video 1",
1515
"https://raw.githubusercontent.com/git-jr/sample-files/main/images/torta.png"
1616
),

app/src/main/java/com/paradoxo/materialgram/di/VideoModule.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.paradoxo.materialgram.di
22

3+
import android.app.Application
4+
import androidx.media3.common.Player
5+
import androidx.media3.exoplayer.ExoPlayer
36
import com.paradoxo.materialgram.data.VideoLocalDataSource
47
import com.paradoxo.materialgram.data.VideoRepository
58
import com.paradoxo.materialgram.data.VideoRepositoryImpl
@@ -32,4 +35,14 @@ object VideoModule {
3235
fun provideVideoDataSource(): VideoLocalDataSource {
3336
return VideoLocalDataSource()
3437
}
38+
39+
40+
@Provides
41+
fun provideExpoVideoPlayer(application: Application): Player {
42+
return ExoPlayer.Builder(application)
43+
.build().apply {
44+
playWhenReady = true
45+
repeatMode = Player.REPEAT_MODE_ALL
46+
}
47+
}
3548
}

app/src/main/java/com/paradoxo/materialgram/presentation/screens/reels/ReelsScreen.kt

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.paradoxo.materialgram.presentation.screens.reels
22

3+
import android.view.ViewGroup
34
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
45
import android.widget.FrameLayout
6+
import androidx.compose.animation.AnimatedVisibility
7+
import androidx.compose.animation.core.tween
8+
import androidx.compose.animation.fadeIn
9+
import androidx.compose.animation.fadeOut
510
import androidx.compose.foundation.ExperimentalFoundationApi
611
import androidx.compose.foundation.background
712
import androidx.compose.foundation.layout.Box
@@ -12,6 +17,7 @@ import androidx.compose.foundation.pager.VerticalPager
1217
import androidx.compose.foundation.pager.rememberPagerState
1318
import androidx.compose.runtime.Composable
1419
import androidx.compose.runtime.DisposableEffect
20+
import androidx.compose.runtime.LaunchedEffect
1521
import androidx.compose.runtime.collectAsState
1622
import androidx.compose.runtime.getValue
1723
import androidx.compose.runtime.mutableStateOf
@@ -28,27 +34,45 @@ import androidx.lifecycle.viewmodel.compose.viewModel
2834
import androidx.media3.common.C
2935
import androidx.media3.common.MediaItem
3036
import androidx.media3.common.Player.REPEAT_MODE_ALL
37+
import androidx.media3.common.util.UnstableApi
3138
import androidx.media3.exoplayer.ExoPlayer
3239
import androidx.media3.ui.AspectRatioFrameLayout
3340
import androidx.media3.ui.PlayerView
3441
import com.paradoxo.materialgram.domain.model.Video
3542

36-
43+
@UnstableApi
3744
@OptIn(ExperimentalFoundationApi::class)
3845
@Composable
3946
fun ReelsScreen() {
4047
val viewModel = viewModel<ReelsViewModel>()
4148
val state by viewModel.uiState.collectAsState()
4249

43-
val pagerState = rememberPagerState(pageCount = {
44-
state.videos.size
45-
})
50+
Box(
51+
modifier = Modifier
52+
.fillMaxSize()
53+
) {
54+
VideoPlayer(viewModel)
55+
56+
LoadOverlay(state)
4657

47-
VerticalPager(state = pagerState) {
48-
Column {
49-
ItemReels(
50-
video = state.videos[pagerState.currentPage]
51-
)
58+
Column(Modifier.fillMaxSize()) {
59+
val pagerState = rememberPagerState(pageCount = {
60+
state.videos.size
61+
})
62+
63+
LaunchedEffect(pagerState.getOffsetFractionForPage(0)) {
64+
val pageIsTotalVisible = pagerState.currentPageOffsetFraction == 0.0f
65+
if (pageIsTotalVisible) {
66+
viewModel.playVideo(state.videos[pagerState.currentPage].url)
67+
viewModel.showLoadAnimation()
68+
}
69+
}
70+
71+
VerticalPager(
72+
state = pagerState,
73+
) {
74+
VideoHud()
75+
}
5276
}
5377
}
5478
}
@@ -123,3 +147,69 @@ private fun ItemReels(video: Video) {
123147
}
124148
}
125149
}
150+
151+
@UnstableApi
152+
@Composable
153+
private fun VideoPlayer(viewModel: ReelsViewModel) {
154+
var lifecycle by remember {
155+
mutableStateOf(Lifecycle.Event.ON_CREATE)
156+
}
157+
val lifecycleOwner = LocalLifecycleOwner.current
158+
DisposableEffect(lifecycleOwner) {
159+
val observer = LifecycleEventObserver { _, event ->
160+
lifecycle = event
161+
}
162+
lifecycleOwner.lifecycle.addObserver(observer)
163+
164+
onDispose {
165+
lifecycleOwner.lifecycle.removeObserver(observer)
166+
}
167+
}
168+
169+
Column(Modifier.fillMaxSize()) {
170+
AndroidView(
171+
factory = { context ->
172+
PlayerView(context).apply {
173+
player = viewModel.player
174+
useController = false
175+
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
176+
layoutParams = FrameLayout.LayoutParams(
177+
ViewGroup.LayoutParams.MATCH_PARENT,
178+
ViewGroup.LayoutParams.MATCH_PARENT
179+
)
180+
}
181+
},
182+
update = {
183+
when (lifecycle) {
184+
Lifecycle.Event.ON_PAUSE -> {
185+
it.onPause()
186+
it.player?.pause()
187+
}
188+
189+
Lifecycle.Event.ON_RESUME -> {
190+
it.onResume()
191+
it.player?.play()
192+
}
193+
194+
else -> Unit
195+
}
196+
}
197+
)
198+
199+
}
200+
}
201+
202+
@Composable
203+
private fun LoadOverlay(state: ReelsUiState) {
204+
AnimatedVisibility(
205+
visible = state.showLoadAnimation,
206+
enter = fadeIn(tween(state.loadAnimationTime / 3)),
207+
exit = fadeOut(tween(state.loadAnimationTime)),
208+
) {
209+
Box(
210+
modifier = Modifier
211+
.fillMaxSize()
212+
.background(Color.Black)
213+
)
214+
}
215+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.paradoxo.materialgram.presentation.screens.reels
2+
3+
import com.paradoxo.materialgram.domain.model.Video
4+
5+
data class ReelsUiState(
6+
val videos: List<Video>,
7+
val showLoadAnimation: Boolean = false,
8+
val loadAnimationTime: Int = 800
9+
)

app/src/main/java/com/paradoxo/materialgram/presentation/screens/reels/ReelsViewModel.kt

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,73 @@ package com.paradoxo.materialgram.presentation.screens.reels
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5+
import androidx.media3.common.MediaItem
6+
import androidx.media3.common.Player
57
import com.paradoxo.materialgram.domain.VideoUseCase
68
import com.paradoxo.materialgram.domain.model.Video
79
import dagger.hilt.android.lifecycle.HiltViewModel
10+
import kotlinx.coroutines.delay
811
import kotlinx.coroutines.flow.MutableStateFlow
912
import kotlinx.coroutines.flow.asStateFlow
1013
import kotlinx.coroutines.launch
1114
import javax.inject.Inject
1215

1316
@HiltViewModel
1417
class ReelsViewModel @Inject constructor(
15-
private val videoUseCase: VideoUseCase
18+
private val videoUseCase: VideoUseCase,
19+
val player: Player
1620
) : ViewModel() {
1721

1822
private val _uiState = MutableStateFlow(ReelsUiState(emptyList()))
1923
val uiState = _uiState.asStateFlow()
2024

2125
init {
26+
player.prepare()
27+
2228
viewModelScope.launch {
2329
loadVideos()
2430
}
2531
}
2632

27-
2833
private suspend fun loadVideos() {
2934
videoUseCase.getVideos().collect { videos ->
3035
_uiState.value = _uiState.value.copy(
3136
videos = videos
3237
)
3338
}
3439
}
35-
}
3640

41+
fun playVideo(path: String) {
42+
val mediaItem = MediaItem.Builder()
43+
.setUri(path)
44+
.build()
45+
player.setMediaItem(mediaItem)
46+
}
47+
48+
49+
private fun resetLoadAnimation() {
50+
_uiState.value = _uiState.value.copy(
51+
showLoadAnimation = !_uiState.value.showLoadAnimation
52+
)
53+
}
54+
55+
fun showLoadAnimation() {
56+
_uiState.value = _uiState.value.copy(
57+
showLoadAnimation = true
58+
)
59+
60+
viewModelScope.launch {
61+
with(_uiState.value) {
62+
if (showLoadAnimation) {
63+
delay(loadAnimationTime.toLong())
64+
resetLoadAnimation()
65+
}
66+
}
67+
}
68+
}
3769

38-
data class ReelsUiState(
39-
val videos: List<Video>
40-
)
70+
override fun onCleared() {
71+
super.onCleared()
72+
player.release()
73+
}
74+
}

0 commit comments

Comments
 (0)