修改优化播放器界面

This commit is contained in:
beyond committed 2026-08-28 06:13:07 +08:00
1 parent 129a2fb028
commit 156a6fe067
9 files changed
+327 -97

No files matched your search

@@ -12,12 +12,14 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -40,6 +42,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -49,6 +52,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
@@ -59,6 +67,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -70,18 +81,11 @@ import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.util.UnstableApi
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.BatteryManager
import android.os.SystemClock
import androidx.compose.foundation.Canvas
import android.os.BatteryManager
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.analytics.AnalyticsListener
@@ -118,6 +122,24 @@ private fun formatNetworkSpeed(bps: Long): String {
else -> "$bps B/s"
}
}
private fun formatVideoCodec(mimeType: String?): String = when (mimeType) {
"video/avc" -> "H.264"
"video/hevc" -> "H.265"
"video/av01" -> "AV1"
"video/x-vnd.on2.vp9" -> "VP9"
"video/x-vnd.on2.vp8" -> "VP8"
"video/mp4v-es" -> "MPEG-4"
"video/mpeg2" -> "MPEG-2"
"video/3gpp" -> "H.263"
"video/x-ms-wmv" -> "WMV"
else -> mimeType?.substringAfterLast('/') ?: ""
}
private fun formatBitrate(bps: Int): String = when {
bps >= 1_000_000 -> String.format("%.1f Mbps", bps / 1_000_000f)
bps >= 1_000 -> String.format("%.0f Kbps", bps / 1_000f)
else -> "$bps bps"
}
private class SpeedMonitor {
private var lastTotalBytes = 0L
@@ -216,14 +238,18 @@ fun VideoPlayerScreen(
var hasNetworkSample by remember { mutableStateOf(false) }
var batteryPercent by remember { mutableIntStateOf(-1) }
var isCharging by remember { mutableStateOf(false) }
var isWifi by remember { mutableStateOf(false) }
var topTimeText by remember { mutableStateOf("") }
var isWifi by remember { mutableStateOf(false) }
var videoCodecText by remember { mutableStateOf("") }
var videoBitrateText by remember { mutableStateOf("") }
var videoFrameRateText by remember { mutableStateOf("") }
var speedUpVisible by remember { mutableStateOf(false) }
var speedUpValue by remember { mutableFloatStateOf(2f) }
var controlsVisible by remember { mutableStateOf(true) }
LaunchedEffect(controlsVisible, isPlaying) {
if (controlsVisible && isPlaying) {
var controlsLocked by remember { mutableStateOf(false) }
LaunchedEffect(controlsVisible, isPlaying, controlsLocked) {
if (controlsVisible && isPlaying && !controlsLocked) {
delay(5000)
controlsVisible = false
}
@@ -241,18 +267,18 @@ fun VideoPlayerScreen(
LaunchedEffect(Unit) {
while (true) {
topTimeText = java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(java.util.Date())
delay(1000)
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
isWifi = cm?.activeNetwork?.let { net ->
cm.getNetworkCapabilities(net)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
} ?: false
delay(2000)
}
}
LaunchedEffect(Unit) {
while (true) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
isWifi = cm?.allNetworks?.any { cap ->
cm.getNetworkCapabilities(cap)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
} ?: false
delay(2000)
topTimeText = java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(java.util.Date())
delay(1000)
}
}
@@ -271,6 +297,18 @@ fun VideoPlayerScreen(
}
}
LaunchedEffect(player) {
while (true) {
val vf = player.videoFormat
if (vf != null) {
videoCodecText = formatVideoCodec(vf.sampleMimeType)
videoBitrateText = formatBitrate(vf.bitrate)
videoFrameRateText = if (vf.frameRate > 0f) String.format("%.0f fps", vf.frameRate) else ""
}
delay(1000)
}
}
LaunchedEffect(Unit) {
while (true) {
val speed = speedMonitor.currentBytesPerSec()
@@ -292,8 +330,19 @@ fun VideoPlayerScreen(
DisposableEffect(Unit) {
val activity = context.findActivity()
val prev = activity?.requestedOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
val window = activity?.window
val insetsController = window?.let { WindowCompat.getInsetsController(it, it.decorView) }
val prevBehavior = insetsController?.systemBarsBehavior
insetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController?.hide(WindowInsetsCompat.Type.systemBars())
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
onDispose { activity?.requestedOrientation = prev }
onDispose {
insetsController?.let { controller ->
controller.show(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = prevBehavior ?: WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
}
activity?.requestedOrientation = prev
}
}
DisposableEffect(playbackUrl) {
playerError = null
@@ -367,7 +416,7 @@ fun VideoPlayerScreen(
.background(Color.Black)
.pointerInput(player) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
val down = awaitFirstDown(requireUnconsumed = true)
val longPress = awaitLongPressOrCancellation(down.id)
if (longPress != null) {
// 长按:2倍速播放,并显示倍速胶囊
@@ -435,7 +484,8 @@ fun VideoPlayerScreen(
enter = fadeIn(),
exit = fadeOut()
) {
Box(Modifier.fillMaxSize()) {
BoxWithConstraints(Modifier.fillMaxSize()) {
val capsuleInset = maxWidth * 0.075f + 20.dp
Box(
Modifier
.fillMaxSize()
@@ -459,7 +509,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Replay,
contentDescription = stringResource(Res.string.player_seek_backward),
active = false,
size = 48.dp,
size = 32.dp,
) {
val pos = player.currentPosition - 10_000L
player.seekTo(maxOf(0L, pos))
@@ -469,7 +519,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Previous,
contentDescription = stringResource(Res.string.player_previous_episode),
active = false,
size = 56.dp,
size = 40.dp,
) {
val idx = episodes.indexOfFirst { it.id == currentEpisodeId }
if (idx > 0) onSelectEpisode(episodes[idx - 1])
@@ -478,8 +528,8 @@ fun VideoPlayerScreen(
icon = if (isPlaying) LumaIcon.Pause else LumaIcon.Play,
contentDescription = stringResource(if (isPlaying) Res.string.player_pause else Res.string.player_play),
active = true,
size = 72.dp,
iconSize = 40.dp,
size = 48.dp,
iconSize = 28.dp,
) {
if (player.isPlaying) {
player.pause()
@@ -491,7 +541,7 @@ fun VideoPlayerScreen(
icon = LumaIcon.Next,
contentDescription = stringResource(Res.string.player_next_episode),
active = false,
size = 56.dp,
size = 40.dp,
) {
val idx = episodes.indexOfFirst { it.id == currentEpisodeId }
if (idx >= 0 && idx < episodes.lastIndex) onSelectEpisode(episodes[idx + 1])
@@ -500,24 +550,25 @@ fun VideoPlayerScreen(
icon = LumaIcon.Speed,
contentDescription = stringResource(Res.string.player_seek_forward),
active = false,
size = 48.dp,
size = 32.dp,
) {
val pos = player.currentPosition + 10_000L
player.seekTo(minOf(pos, player.duration))
}
}
Column(Modifier.fillMaxWidth().statusBarsPadding().padding(16.dp)) {
Column(Modifier.fillMaxWidth().statusBarsPadding().padding(top = 16.dp)) {
// 第一排:当前时间(左)|网速 + 电量(右)
Row(
Modifier.fillMaxWidth(),
Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(topTimeText, color = Color.White, fontSize = 13.sp)
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
if (playbackUrl != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
LumaIconView(LumaIcon.Network, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
LumaIconView(if (isWifi) LumaIcon.Wifi else LumaIcon.Signal, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(4.dp))
if (hasNetworkSample) Text(formatNetworkSpeed(networkSpeed), color = Color.White, fontSize = 13.sp)
}
@@ -531,28 +582,32 @@ fun VideoPlayerScreen(
}
}
}
Spacer(Modifier.height(10.dp))
Spacer(Modifier.height(8.dp))
// 第二排:关闭按钮(左)|Emby 艺术字(紧跟关闭按钮右侧)|字幕/剧集按钮(右)
Row(
Modifier.fillMaxWidth(),
Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(Modifier.weight(1f).padding(end = 12.dp), verticalArrangement = Arrangement.Center) {
if (titleLogoUrl.isNotBlank()) {
AsyncImage(
model = titleLogoUrl,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.height(28.dp).widthIn(max = 220.dp),
)
} else {
Text(title, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
if (serverName.isNotBlank()) {
Text(serverName, color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
LiquidSurfaceButton(
icon = LumaIcon.Close,
contentDescription = stringResource(Res.string.player_close),
active = false,
size = 40.dp,
) { onClose() }
if (titleLogoUrl.isNotBlank()) {
AsyncImage(
model = titleLogoUrl,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.height(28.dp).widthIn(max = 220.dp),
)
} else {
Text(title, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
LiquidSurfaceButton(
icon = LumaIcon.Subtitle,
contentDescription = stringResource(Res.string.player_subtitle),
@@ -571,6 +626,26 @@ fun VideoPlayerScreen(
}
}
}
if (serverName.isNotBlank()) {
Spacer(Modifier.height(8.dp))
// 第三排:服务器图标 + 服务器名字
Row(Modifier.fillMaxWidth().padding(start = capsuleInset + 52.dp, end = capsuleInset), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(30.dp).clip(CircleShape), contentAlignment = Alignment.Center) {
if (serverIconUrl.isNotBlank()) {
AsyncImage(
model = serverIconUrl,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
} else {
LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(24.dp))
}
}
Spacer(Modifier.width(8.dp))
Text(serverName, color = Color.White.copy(alpha = 0.8f), fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
if (playbackUrl != null) {
@@ -578,56 +653,73 @@ fun VideoPlayerScreen(
Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth(0.85f)
.padding(bottom = 32.dp)
.lumaGlass(
backdrop = null,
radius = 24.dp,
level = LumaGlassLevel.Strong,
tint = Color.Black.copy(alpha = 0.40f),
borderColor = Color.White.copy(alpha = 0.16f),
shadowElevation = 4.dp,
)
.padding(horizontal = 20.dp, vertical = 12.dp),
.padding(bottom = 32.dp),
) {
var totalDurationMs by remember { mutableLongStateOf(0L) }
var currentPositionMs by remember { mutableLongStateOf(0L) }
var isSeeking by remember { mutableStateOf(false) }
var seekPosition by remember { mutableFloatStateOf(0f) }
var bufferProgress by remember { mutableFloatStateOf(0f) }
LaunchedEffect(player) {
while (true) {
if (player.duration > 0) {
totalDurationMs = player.duration
if (!isSeeking) {
currentPositionMs = player.currentPosition
progress = player.currentPosition.toFloat() / player.duration.toFloat()
val b = (player.bufferedPosition.toFloat() / player.duration).coerceIn(0f, 1f)
bufferProgress = maxOf(b, progress)
}
}
delay(250)
}
// 技术信息行(进度胶囊上方、胶囊外部):内核|格式|码率|帧率
Row(
Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Text("ExoPlayer", color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
if (videoCodecText.isNotBlank()) Text(videoCodecText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
if (videoBitrateText.isNotBlank()) Text(videoBitrateText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
if (videoFrameRateText.isNotBlank()) Text(videoFrameRateText, color = Color.White.copy(alpha = 0.75f), fontSize = 12.sp)
}
Column(
Modifier
.fillMaxWidth()
.lumaGlass(
backdrop = null,
radius = 24.dp,
level = LumaGlassLevel.Strong,
tint = Color.Black.copy(alpha = 0.40f),
borderColor = Color.White.copy(alpha = 0.22f),
shadowElevation = 4.dp,
)
.padding(horizontal = 16.dp, vertical = 6.dp),
) {
var totalDurationMs by remember { mutableLongStateOf(0L) }
var currentPositionMs by remember { mutableLongStateOf(0L) }
var isSeeking by remember { mutableStateOf(false) }
var seekPosition by remember { mutableFloatStateOf(0f) }
var bufferProgress by remember { mutableFloatStateOf(0f) }
Row(verticalAlignment = Alignment.CenterVertically) {
Text(formatMs(currentPositionMs), color = Color.White, fontSize = 12.sp)
LumaSeekBar(
value = if (isSeeking) seekPosition else progress,
onValueChange = { v -> isSeeking = true; seekPosition = v },
onValueChangeFinished = {
player.seekTo((seekPosition * player.duration).toLong())
isSeeking = false
latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, "TimeUpdate")
},
modifier = Modifier.weight(1f).padding(horizontal = 12.dp),
activeColor = Color.White,
trackColor = Color.White.copy(alpha = 0.12f),
bufferColor = Color.White.copy(alpha = 0.28f),
bufferedValue = bufferProgress,
thumbColor = Color.White,
)
Text(formatMs(totalDurationMs), color = Color.White, fontSize = 12.sp)
LaunchedEffect(player) {
while (true) {
if (player.duration > 0) {
totalDurationMs = player.duration
if (!isSeeking) {
currentPositionMs = player.currentPosition
progress = player.currentPosition.toFloat() / player.duration.toFloat()
val b = (player.bufferedPosition.toFloat() / player.duration).coerceIn(0f, 1f)
bufferProgress = maxOf(b, progress)
}
}
delay(250)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(formatMs(currentPositionMs), color = Color.White, fontSize = 12.sp)
LumaSeekBar(
value = if (isSeeking) seekPosition else progress,
onValueChange = { v -> isSeeking = true; seekPosition = v; controlsLocked = true },
onValueChangeFinished = {
player.seekTo((seekPosition * player.duration).toLong())
isSeeking = false
controlsLocked = false
latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, "TimeUpdate")
},
modifier = Modifier.weight(1f).padding(horizontal = 12.dp),
activeColor = Color.White,
trackColor = Color.White.copy(alpha = 0.12f),
bufferColor = Color.White.copy(alpha = 0.45f),
bufferedValue = bufferProgress,
thumbColor = Color.White,
)
Text(formatMs(totalDurationMs), color = Color.White, fontSize = 12.sp)
}
}
}
}
@@ -750,6 +842,7 @@ private fun LiquidGlassPanel(
) {
Column(
Modifier
.padding(top = 16.dp, bottom = 16.dp, end = 16.dp)
.fillMaxHeight()
.width(320.dp)
.lumaGlass(
@@ -176,7 +176,7 @@ fun LumaSeekBar(
Canvas(
modifier
.heightIn(min = 48.dp)
.heightIn(min = 30.dp)
.onSizeChanged { trackWidthPx = it.width.toFloat() }
.semantics {
progressBarRangeInfo = ProgressBarRangeInfo(currentValue, 0f..1f)
@@ -189,6 +189,7 @@ fun LumaSeekBar(
.pointerInput(trackWidthPx) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
down.consume()
dragging = true
updateFromPosition(down.position.x)
var lastX = down.position.x
@@ -54,6 +54,8 @@ enum class LumaIcon(
Database(R.drawable.aurora_database, R.drawable.aurora_database_selected),
Cloud(R.drawable.aurora_cloud, R.drawable.aurora_cloud_selected),
Network(R.drawable.aurora_network, R.drawable.aurora_network_selected),
Wifi(R.drawable.aurora_wifi, R.drawable.aurora_wifi_selected),
Signal(R.drawable.aurora_signal, R.drawable.aurora_signal_selected),
Globe(R.drawable.aurora_globe, R.drawable.aurora_globe_selected),
Shield(R.drawable.aurora_shield, R.drawable.aurora_shield_selected),
Key(R.drawable.aurora_key, R.drawable.aurora_key_selected),
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M22 62 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M40 48 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M58 34 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M76 20 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M22 62 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M40 48 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M58 34 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M76 20 V80"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M14 36 Q50 4 86 36"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M29 51 Q50 27 71 51"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M43 63 Q50 52 57 63"
android:fillColor="@android:color/transparent"
android:strokeColor="#1C1C1E"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M45 78 a5 5 0 1 0 10 0 a5 5 0 1 0 -10 0 Z"
android:fillColor="#1C1C1E" />
</vector>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M14 36 Q50 4 86 36"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M29 51 Q50 27 71 51"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M43 63 Q50 52 57 63"
android:fillColor="@android:color/transparent"
android:strokeColor="#0A84FF"
android:strokeWidth="6.0"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M45 78 a5 5 0 1 0 10 0 a5 5 0 1 0 -10 0 Z"
android:fillColor="#0A84FF" />
</vector>
+1
View File
@@ -137,6 +137,7 @@
<string name="player_failed">Playback failed</string>
<string name="player_subtitle">Subtitles</string>
<string name="player_episode_list">Episode list</string>
<string name="player_close">Close</string>
<string name="player_seek_backward">Seek back 10 seconds</string>
<string name="player_previous_episode">Previous episode</string>
<string name="player_play">Play</string>
+1
View File
@@ -137,6 +137,7 @@
<string name="player_failed">播放失败</string>
<string name="player_subtitle">字幕</string>
<string name="player_episode_list">剧集列表</string>
<string name="player_close">关闭</string>
<string name="player_seek_backward">后退 10 秒</string>
<string name="player_previous_episode">上一集</string>
<string name="player_play">播放</string>