Код: Выделить всё
@SuppressLint("UnsafeOptInUsageError")
class ExoPlayersPool(
private val context: Context,
private val videos: List,
) {
private val players = hashMapOf()
fun getExoInstance(index: Int) = players[index].takeIf {
it != null
} ?: run {
val videoUrl = videos[index]
players[index]?.release()
val mediaSource = MediaItem.Builder()
.setUri(Uri.parse(videoUrl))
.setMimeType(getMimeTypeFromUrl(videoUrl))
.build()
val exoPlayer = ExoPlayer.Builder(context)
.build().apply {
repeatMode = Player.REPEAT_MODE_ALL
volume = 1f
seekTo(0)
setMediaItem(mediaSource)
prepare()
}
players[index] = exoPlayer
exoPlayer
}
fun releasePlayer(index: Int) {
val player = players[index]
player?.release()
players[index] = null
}
fun releaseAll() {
players.forEach {
it.value?.release()
}
players.clear()
}
private fun getMimeTypeFromUrl(videoUrl: String): String? {
val videoUri = Uri.parse(videoUrl)
val inferContentType = Util.inferContentType(
videoUri,
null
)
return Util.getAdaptiveMimeTypeForContentType(inferContentType)
.takeIf { it?.isNotEmpty() == true } ?: run {
val extension = MimeTypeMap.getFileExtensionFromUrl(
videoUri.encodedPath
)
MimeTypeMap.getSingleton().getMimeTypeFromExtension(
extension.lowercase(Locale.getDefault())
)
}
}
}
Код: Выделить всё
@Composable
@OptIn(ExperimentalFoundationApi::class)
private fun HomeScreen() {
val videos = listOf(
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4"
)
val pagerState = rememberPagerState(pageCount = { videos.size })
val coroutineScope = rememberCoroutineScope()
var selectedPage by remember { mutableIntStateOf(0) }
val onSwipe = {
coroutineScope.launch {
val targetPage = (selectedPage + 1).takeIf {
selectedPage < videos.size - 1
}.orZero()
pagerState.animateScrollToPage(targetPage)
if (targetPage == 0) {
pagerState.scrollToPage(0)
}
}
}
LaunchedEffect(key1 = pagerState) {
snapshotFlow {
pagerState.currentPage
}.collect { page ->
selectedPage = page
}
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
HeaderSection(
pagerState = pagerState,
videos = videos,
onSwipe = onSwipe
)
}
items(10) { index ->
FillSection(index)
}
}
}
@Composable
private fun FillSection(index: Int) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.background(
color = if (index % 2 == 0) {
Color.Gray
} else {
Color.Blue
}
)
)
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
private fun HeaderSection(
pagerState: PagerState,
videos: List,
onSwipe: () -> Job
) {
Box(modifier = Modifier.fillMaxSize()) {
val exoPlayersPool = rememberExoPlayersPool(
pages = videos,
)
HorizontalPager(
modifier = Modifier
.fillMaxWidth()
.height(350.dp),
state = pagerState,
beyondBoundsPageCount = 1
) { index ->
ExoplayerContent(videoUrl = videos[index],
getExoInstance = {
exoPlayersPool.getExoInstance(index)
}, onRelease = {
exoPlayersPool.releasePlayer(index)
}
)
}
HomeBannersPageIndicator(
pagerState = pagerState
) {
onSwipe()
}
}
}
Проблема в том, что при первой загрузке все работает нормально, но в тот момент, когда карусель доходит до последнего видео и возвращается к первому, экран становится черным, хотя звук видео все равно воспроизводится:
Код: Выделить всё
@Composable
fun ExoplayerContent(
videoUrl: String,
modifier: Modifier = Modifier,
getExoInstance: () -> ExoPlayer,
onRelease: () -> Unit,
onError: (Throwable) -> Unit = {}
) {
Box(
modifier = modifier.fillMaxSize()
) {
val isVideoUrl = MediaViewUtils.isVideo(url = videoUrl)
if (isVideoUrl) {
ExoPlayerView(
player = getExoInstance(),
modifier = modifier,
onRelease = onRelease,
onError = onError
)
}
}
}
@SuppressLint("RestrictedApi")
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BoxScope.HomeBannersPageIndicator(
pagerState: PagerState,
onAnimationFinished: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(20.dp)
.align(Alignment.BottomStart),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
var currentPage by remember { mutableIntStateOf(0) }
var progress by remember { mutableFloatStateOf(0.0f) }
LaunchedEffect(
key1 = pagerState.currentPage,
key2 = pagerState.isScrollInProgress
) {
if (pagerState.currentPage != currentPage) {
currentPage = pagerState.currentPage
progress = 0.0f
}
while (progress = 1.0f) {
onAnimationFinished()
}
}
repeat(pagerState.pageCount) { index ->
LinearProgressIndicator(
modifier = Modifier
.weight(1f)
.height(2.dp),
progress = progress.takeIf {
currentPage == index
} ?: 0F,
color = Color.White,
trackColor = Color.Gray.copy(
alpha = 0.5f
)
)
}
}
}
@Composable
fun rememberExoPlayersPool(
pages: List,
): ExoPlayersPool {
val context = LocalContext.current
val playersPool = remember {
ExoPlayersPool(
context = context,
videos = pages
)
}
DisposableEffect(Unit) {
onDispose {
playersPool.releaseAll()
}
}
return playersPool
}
Подробнее здесь: https://stackoverflow.com/questions/792 ... nalsurface
Мобильная версия