feat:视频播放效果优化

This commit is contained in:
amos
2026-02-28 17:47:16 +08:00
parent c696c3f8f6
commit ed615a1f94
4 changed files with 285 additions and 12 deletions

View File

@@ -13,11 +13,11 @@ android {
applicationId = "com.memory.app"
minSdk = 26
targetSdk = 35
versionCode = 53
versionName = "1.6.9"
versionCode = 67
versionName = "1.8.3"
buildConfigField("String", "API_BASE_URL", "\"https://x.amos.us.kg/api/\"")
buildConfigField("int", "VERSION_CODE", "53")
buildConfigField("int", "VERSION_CODE", "67")
}
signingConfigs {

View File

@@ -3,20 +3,35 @@ package com.memory.app.ui.components
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FastForward
import androidx.compose.material.icons.filled.FastRewind
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
@Composable
fun VideoPlayer(
videoUrl: String,
@@ -24,6 +39,10 @@ fun VideoPlayer(
) {
val context = LocalContext.current
var isLoading by remember { mutableStateOf(true) }
var isPlaying by remember { mutableStateOf(true) }
var currentPosition by remember { mutableStateOf(0L) }
var duration by remember { mutableStateOf(0L) }
var showControls by remember { mutableStateOf(true) }
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
@@ -35,27 +54,46 @@ fun VideoPlayer(
override fun onPlaybackStateChanged(playbackState: Int) {
isLoading = playbackState == Player.STATE_BUFFERING
}
override fun onIsPlayingChanged(playing: Boolean) {
isPlaying = playing
}
})
}
}
// 更新进度
LaunchedEffect(exoPlayer) {
while (isActive) {
currentPosition = exoPlayer.currentPosition
duration = exoPlayer.duration.coerceAtLeast(0L)
delay(100)
}
}
// 自动隐藏控制器
LaunchedEffect(showControls, isPlaying) {
if (showControls && isPlaying) {
delay(3000)
showControls = false
}
}
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
}
Box(modifier = modifier) {
Box(
modifier = modifier
.clickable { showControls = !showControls }
) {
AndroidView(
factory = { ctx ->
PlayerView(ctx).apply {
player = exoPlayer
useController = true
// 设置控制器显示时间和行为
controllerShowTimeoutMs = 3000
controllerHideOnTouch = true
controllerAutoShow = true
// 使用默认控制器ExoPlayer 的默认控制器已经在底部
useController = false
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
@@ -75,5 +113,133 @@ fun VideoPlayer(
CircularProgressIndicator(color = Color.White)
}
}
// 自定义控制器
if (showControls) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f))
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(bottom = 40.dp),
verticalArrangement = Arrangement.Bottom
) {
// 播放控制按钮
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
exoPlayer.seekTo((exoPlayer.currentPosition - 15000).coerceAtLeast(0))
},
modifier = Modifier.size(48.dp)
) {
Icon(
Icons.Default.FastRewind,
contentDescription = "后退15秒",
tint = Color.White,
modifier = Modifier.size(32.dp)
)
}
Spacer(modifier = Modifier.width(24.dp))
IconButton(
onClick = {
if (isPlaying) {
exoPlayer.pause()
} else {
exoPlayer.play()
}
},
modifier = Modifier.size(64.dp)
) {
Icon(
if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) "暂停" else "播放",
tint = Color.White,
modifier = Modifier.size(48.dp)
)
}
Spacer(modifier = Modifier.width(24.dp))
IconButton(
onClick = {
exoPlayer.seekTo((exoPlayer.currentPosition + 15000).coerceAtMost(duration))
},
modifier = Modifier.size(48.dp)
) {
Icon(
Icons.Default.FastForward,
contentDescription = "前进15秒",
tint = Color.White,
modifier = Modifier.size(32.dp)
)
}
}
// 进度条
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = formatTime(currentPosition),
color = Color.White,
fontSize = 12.sp,
modifier = Modifier.padding(end = 8.dp)
)
Slider(
value = if (duration > 0) currentPosition.toFloat() / duration else 0f,
onValueChange = { value ->
val newPosition = (value * duration).toLong()
exoPlayer.seekTo(newPosition)
},
modifier = Modifier
.weight(1f)
.height(20.dp),
colors = SliderDefaults.colors(
thumbColor = Color.White,
activeTrackColor = Color.White,
inactiveTrackColor = Color.White.copy(alpha = 0.3f)
),
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(2.dp)
)
}
)
Text(
text = formatTime(duration),
color = Color.White,
fontSize = 12.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
}
}
private fun formatTime(timeMs: Long): String {
if (timeMs <= 0) return "0:00"
val totalSeconds = timeMs / 1000
val minutes = totalSeconds / 60
val seconds = totalSeconds % 60
return String.format("%d:%02d", minutes, seconds)
}

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#CC000000"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="12dp"
android:paddingRight="16dp"
android:paddingBottom="20dp">
<!-- 播放控制按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingBottom="20dp">
<ImageButton
android:id="@id/exo_rew"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginEnd="20dp"
android:background="@android:color/transparent"
android:contentDescription="后退15秒"
android:scaleType="fitCenter"
android:src="@drawable/exo_icon_rewind"
android:tint="#FFFFFF" />
<ImageButton
android:id="@id/exo_play_pause"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@android:color/transparent"
android:contentDescription="播放/暂停"
android:scaleType="fitCenter"
android:src="@drawable/exo_icon_play"
android:tint="#FFFFFF" />
<ImageButton
android:id="@id/exo_ffwd"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="20dp"
android:background="@android:color/transparent"
android:contentDescription="前进15秒"
android:scaleType="fitCenter"
android:src="@drawable/exo_icon_fastforward"
android:tint="#FFFFFF" />
</LinearLayout>
<!-- 进度条区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="10dp"
android:text="00:00"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold" />
<androidx.media3.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="26dp"
android:layout_weight="1" />
<TextView
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:text="00:00"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.media3.ui.PlayerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_custom_playback_control"
app:show_buffering="when_playing"
app:show_timeout="3000"
app:use_controller="true" />