Skip to content

Commit 52dc365

Browse files
authored
Merge pull request #1657 from DimensionDev/feature/video_playback
make video player singleton
2 parents f3f434f + cbe4393 commit 52dc365

File tree

3 files changed

+353
-263
lines changed

3 files changed

+353
-263
lines changed

app/src/main/java/dev/dimension/flare/di/AndroidModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import dev.dimension.flare.common.ComposeInAppNotification
55
import dev.dimension.flare.common.InAppNotification
66
import dev.dimension.flare.common.PodcastManager
77
import dev.dimension.flare.common.VideoDownloadHelper
8-
import dev.dimension.flare.ui.component.VideoPlayerPool
8+
import dev.dimension.flare.ui.component.SurfaceBindingManager
99
import org.koin.core.module.dsl.singleOf
1010
import org.koin.dsl.binds
1111
import org.koin.dsl.module
1212

1313
@UnstableApi
1414
val androidModule =
1515
module {
16-
singleOf(::VideoPlayerPool)
16+
singleOf(::SurfaceBindingManager)
1717
singleOf(::ComposeInAppNotification) binds arrayOf(InAppNotification::class, ComposeInAppNotification::class)
1818
singleOf(::VideoDownloadHelper)
1919
singleOf(::PodcastManager)

app/src/main/java/dev/dimension/flare/ui/screen/media/StatusMediaScreen.kt

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,18 @@ import androidx.compose.foundation.layout.size
3333
import androidx.compose.foundation.layout.systemBars
3434
import androidx.compose.foundation.layout.systemBarsPadding
3535
import androidx.compose.foundation.layout.width
36+
import androidx.compose.foundation.layout.widthIn
3637
import androidx.compose.foundation.layout.windowInsetsPadding
3738
import androidx.compose.foundation.pager.HorizontalPager
3839
import androidx.compose.foundation.pager.rememberPagerState
3940
import androidx.compose.foundation.rememberScrollState
4041
import androidx.compose.foundation.shape.CircleShape
4142
import androidx.compose.foundation.verticalScroll
4243
import androidx.compose.material3.ExperimentalMaterial3Api
44+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
4345
import androidx.compose.material3.Icon
4446
import androidx.compose.material3.IconButton
47+
import androidx.compose.material3.LinearWavyProgressIndicator
4548
import androidx.compose.material3.ListItem
4649
import androidx.compose.material3.ListItemDefaults
4750
import androidx.compose.material3.MaterialTheme
@@ -98,8 +101,8 @@ import dev.dimension.flare.model.MicroBlogKey
98101
import dev.dimension.flare.ui.component.FAIcon
99102
import dev.dimension.flare.ui.component.Glassify
100103
import dev.dimension.flare.ui.component.LocalComponentAppearance
104+
import dev.dimension.flare.ui.component.SurfaceBindingManager
101105
import dev.dimension.flare.ui.component.VideoPlayer
102-
import dev.dimension.flare.ui.component.VideoPlayerPool
103106
import dev.dimension.flare.ui.component.platform.isBigScreen
104107
import dev.dimension.flare.ui.component.status.CommonStatusComponent
105108
import dev.dimension.flare.ui.humanizer.humanize
@@ -151,7 +154,7 @@ internal fun StatusMediaScreen(
151154
onDismiss: () -> Unit,
152155
toAltText: (UiMedia) -> Unit,
153156
uriHandler: UriHandler,
154-
playerPool: VideoPlayerPool = koinInject(),
157+
surfaceBindingManager: SurfaceBindingManager = koinInject(),
155158
) {
156159
val isBigScreen = isBigScreen()
157160
val hapticFeedback = LocalHapticFeedback.current
@@ -278,14 +281,11 @@ internal fun StatusMediaScreen(
278281
uri = media.url,
279282
previewUri = media.thumbnailUrl,
280283
contentDescription = media.description,
284+
aspectRatio = media.aspectRatio,
281285
autoPlay = true,
282-
modifier =
283-
Modifier
284-
.fillMaxSize(),
285286
onClick = {
286287
state.setShowUi(!state.showUi)
287288
},
288-
aspectRatio = media.aspectRatio,
289289
showControls = true,
290290
keepScreenOn = true,
291291
muted = false,
@@ -303,9 +303,6 @@ internal fun StatusMediaScreen(
303303
previewUri = null,
304304
contentDescription = media.description,
305305
autoPlay = false,
306-
modifier =
307-
Modifier
308-
.fillMaxSize(),
309306
onClick = {
310307
state.setShowUi(!state.showUi)
311308
},
@@ -511,15 +508,12 @@ internal fun StatusMediaScreen(
511508
medias[state.currentPage]
512509
}
513510
if (current is UiMedia.Video) {
514-
val player = playerPool.peek(current.url)
515-
if (player != null) {
516-
PlayerControl(
517-
player,
518-
modifier =
519-
Modifier
520-
.padding(end = screenHorizontalPadding),
521-
)
522-
}
511+
PlayerControl(
512+
surfaceBindingManager.player,
513+
modifier =
514+
Modifier
515+
.widthIn(max = 480.dp),
516+
)
523517
}
524518
}
525519
if (!isBigScreen) {
@@ -677,74 +671,91 @@ internal fun StatusMediaScreen(
677671
}
678672

679673
@androidx.annotation.OptIn(UnstableApi::class)
680-
@ExperimentalMaterial3Api
674+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
681675
@Composable
682676
private fun PlayerControl(
683677
player: ExoPlayer,
684678
modifier: Modifier = Modifier,
685679
) {
686680
val playPauseButtonState = rememberPlayPauseButtonState(player)
687-
var time by remember { mutableStateOf("") }
688-
var isSliderChanging by remember {
689-
mutableStateOf(false)
690-
}
691-
var sliderValue by remember {
692-
mutableStateOf(0f)
693-
}
694-
if (!playPauseButtonState.showPlay && !isSliderChanging) {
695-
LaunchedEffect(Unit) {
696-
while (true) {
697-
sliderValue = player.currentPosition.toFloat() / player.duration.toFloat()
698-
time =
699-
buildString {
700-
append(player.currentPosition.milliseconds.humanize())
701-
append(" / ")
702-
append(player.duration.milliseconds.humanize())
703-
}
704-
awaitFrame()
705-
}
681+
var isLoaded by remember { mutableStateOf(false) }
682+
LaunchedEffect(player) {
683+
while (!isLoaded) {
684+
isLoaded = player.isPlaying
685+
awaitFrame()
706686
}
707687
}
708688
Row(
709689
modifier = modifier,
710690
verticalAlignment = Alignment.CenterVertically,
711691
horizontalArrangement = Arrangement.spacedBy(8.dp),
712692
) {
713-
IconButton(
714-
onClick = {
715-
playPauseButtonState.onClick()
716-
},
717-
enabled = playPauseButtonState.isEnabled,
718-
) {
719-
Icon(
720-
if (playPauseButtonState.showPlay) {
721-
FontAwesomeIcons.Solid.Play
722-
} else {
723-
FontAwesomeIcons.Solid.Pause
693+
if (!isLoaded) {
694+
LinearWavyProgressIndicator(
695+
modifier =
696+
Modifier
697+
.fillMaxWidth()
698+
.padding(16.dp),
699+
)
700+
} else {
701+
var time by remember { mutableStateOf("") }
702+
var isSliderChanging by remember {
703+
mutableStateOf(false)
704+
}
705+
var sliderValue by remember {
706+
mutableStateOf(0f)
707+
}
708+
if (!playPauseButtonState.showPlay && !isSliderChanging) {
709+
LaunchedEffect(Unit) {
710+
while (true) {
711+
sliderValue = player.currentPosition.toFloat() / player.duration.toFloat()
712+
time =
713+
buildString {
714+
append(player.currentPosition.milliseconds.humanize())
715+
append(" / ")
716+
append(player.duration.milliseconds.humanize())
717+
}
718+
awaitFrame()
719+
}
720+
}
721+
}
722+
IconButton(
723+
onClick = {
724+
playPauseButtonState.onClick()
724725
},
725-
contentDescription = null,
726-
modifier = Modifier.size(16.dp),
726+
enabled = playPauseButtonState.isEnabled,
727+
) {
728+
Icon(
729+
if (playPauseButtonState.showPlay) {
730+
FontAwesomeIcons.Solid.Play
731+
} else {
732+
FontAwesomeIcons.Solid.Pause
733+
},
734+
contentDescription = null,
735+
modifier = Modifier.size(16.dp),
736+
)
737+
}
738+
Slider(
739+
value = sliderValue,
740+
onValueChange = {
741+
isSliderChanging = true
742+
sliderValue = it
743+
time =
744+
buildString {
745+
append((player.duration * it).toLong().milliseconds.humanize())
746+
append(" / ")
747+
append(player.duration.milliseconds.humanize())
748+
}
749+
},
750+
onValueChangeFinished = {
751+
player.seekTo((player.duration * sliderValue).toLong())
752+
isSliderChanging = false
753+
},
754+
modifier = Modifier.weight(1f),
727755
)
756+
Text(time)
757+
Spacer(Modifier.width(screenHorizontalPadding))
728758
}
729-
Slider(
730-
value = sliderValue,
731-
onValueChange = {
732-
isSliderChanging = true
733-
sliderValue = it
734-
time =
735-
buildString {
736-
append((player.duration * it).toLong().milliseconds.humanize())
737-
append(" / ")
738-
append(player.duration.milliseconds.humanize())
739-
}
740-
},
741-
onValueChangeFinished = {
742-
player.seekTo((player.duration * sliderValue).toLong())
743-
isSliderChanging = false
744-
},
745-
modifier = Modifier.weight(1f),
746-
)
747-
Text(time)
748759
}
749760
}
750761

0 commit comments

Comments
 (0)