mirror of
https://github.com/InvertGeek/MixFile.git
synced 2026-09-03 07:18:45 +08:00
+track selectors for player
This commit is contained in:
@@ -1,19 +1,109 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("com.android.application") version "8.10.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.0" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false
|
||||
id("com.android.application") version "8.10.1"
|
||||
id("org.jetbrains.kotlin.android") version "2.2.0"
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.2.0"
|
||||
id("com.google.gms.google-services")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "2.2.0"
|
||||
}
|
||||
buildscript {
|
||||
repositories {
|
||||
// Check that you have the following line (if not, add it):
|
||||
|
||||
google() // Google's Maven repository
|
||||
maven("https://jitpack.io")
|
||||
android {
|
||||
namespace = "com.donut.mixfile"
|
||||
compileSdk = 36
|
||||
defaultConfig {
|
||||
applicationId = "com.donut.mixfile"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 160
|
||||
versionName = "2.0.11.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
|
||||
classpath("com.google.gms:google-services:4.4.3")
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
variant.outputs
|
||||
.map { it as BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
val outputFileName =
|
||||
"MixFile-${variant.baseName}-${variant.versionName}.apk"
|
||||
output.outputFileName = outputFileName
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_1_8)
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
// 引用 libs 目录下的所有 aar
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
implementation("com.github.InvertGeek:mixfile-core:2.0.11")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||
implementation("com.tencent:mmkv:1.3.14")
|
||||
implementation("net.engawapg.lib:zoomable:1.6.1")
|
||||
implementation("io.coil-kt:coil:2.6.0")
|
||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||
implementation("io.coil-kt:coil-gif:2.6.0")
|
||||
implementation("io.coil-kt:coil-svg:2.6.0")
|
||||
implementation("io.coil-kt:coil-video:2.6.0")
|
||||
|
||||
implementation("androidx.media3:media3-exoplayer:1.10.0") // [Required] androidx.media3 ExoPlayer dependency
|
||||
implementation("androidx.media3:media3-session:1.10.0") // [Required] MediaSession Extension dependency
|
||||
implementation("androidx.media3:media3-ui:1.10.0") // [Required] Base Player UI
|
||||
implementation("androidx.navigation:navigation-compose:2.9.3")
|
||||
implementation("com.google.firebase:firebase-analytics:23.0.0")
|
||||
implementation("androidx.core:core-ktx:1.17.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2")
|
||||
implementation("androidx.activity:activity-compose:1.10.1")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation(platform("androidx.compose:compose-bom:2024.08.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
|
||||
androidTestImplementation("androidx.test.ext:junit:1.3.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2024.08.00"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
}
|
||||
@@ -6,39 +6,30 @@ import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.component.common.SingleSelectItemList
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BottomControl(
|
||||
visible: Boolean,
|
||||
@@ -50,12 +41,8 @@ fun BottomControl(
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { it } // 从顶部(负方向)滑入
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
targetOffsetY = { it } // 向上(负方向)滑出
|
||||
),
|
||||
enter = slideInVertically { it },
|
||||
exit = slideOutVertically { it },
|
||||
modifier = modifier
|
||||
) {
|
||||
Column(
|
||||
@@ -64,95 +51,40 @@ fun BottomControl(
|
||||
.background(Color.Black.copy(alpha = 0.1f))
|
||||
.padding(5.dp),
|
||||
) {
|
||||
|
||||
|
||||
Slider(
|
||||
value = progress,
|
||||
onValueChange = { newValue ->
|
||||
player.pause()
|
||||
// 1. 进度条:修复了回调逻辑,通过 onTrackTimeChange 同步状态
|
||||
PlayerProgressSlider(
|
||||
progress = progress,
|
||||
onSeek = { newValue ->
|
||||
player.seekTo((player.duration * newValue).toLong())
|
||||
onTrackTimeChange(newValue)
|
||||
},
|
||||
onValueChangeFinished = {
|
||||
player.play()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
thumb = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(15.dp) // 自定义滑块大小
|
||||
.align(Alignment.Center)
|
||||
.background(Color.White, CircleShape)
|
||||
)
|
||||
}
|
||||
},
|
||||
track = { sliderState ->
|
||||
SliderDefaults.Track(
|
||||
sliderState = sliderState
|
||||
)
|
||||
},
|
||||
onSeekFinished = { player.play() }
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
.padding(bottom = 10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 左侧:播放控制与时间
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(15.dp)
|
||||
) {
|
||||
var lastSeek = remember { System.currentTimeMillis() }
|
||||
if (player.mediaItemCount > 1) {
|
||||
IconButton(
|
||||
modifier = Modifier.scale(1f),
|
||||
onClick = {
|
||||
if (System.currentTimeMillis() - lastSeek < 500) {
|
||||
return@IconButton
|
||||
}
|
||||
lastSeek = System.currentTimeMillis()
|
||||
player.seekToPreviousMediaItem()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(100.dp),
|
||||
imageVector = Icons.Default.SkipPrevious,
|
||||
contentDescription = "Previous",
|
||||
tint = Color.White
|
||||
)
|
||||
IconButton(onClick = { player.seekToPreviousMediaItem() }) {
|
||||
Icon(Icons.Default.SkipPrevious, "Previous", tint = Color.White)
|
||||
}
|
||||
IconButton(
|
||||
modifier = Modifier.scale(1f),
|
||||
onClick = {
|
||||
if (System.currentTimeMillis() - lastSeek < 500) {
|
||||
return@IconButton
|
||||
}
|
||||
lastSeek = System.currentTimeMillis()
|
||||
player.seekToNextMediaItem()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(100.dp),
|
||||
imageVector = Icons.Default.SkipNext,
|
||||
contentDescription = "Next",
|
||||
tint = Color.White
|
||||
)
|
||||
IconButton(onClick = { player.seekToNextMediaItem() }) {
|
||||
Icon(Icons.Default.SkipNext, "Next", tint = Color.White)
|
||||
}
|
||||
} else {
|
||||
Row(modifier = Modifier.width(10.dp)) { }
|
||||
}
|
||||
Text(
|
||||
text = "${formatTime((player.duration * progress).toLong())}/${
|
||||
formatTime(
|
||||
player.duration.coerceAtLeast(
|
||||
0
|
||||
)
|
||||
player.duration.coerceAtLeast(0)
|
||||
)
|
||||
}",
|
||||
color = Color.White,
|
||||
@@ -160,91 +92,133 @@ fun BottomControl(
|
||||
)
|
||||
}
|
||||
|
||||
// 右侧:轨道、选集、速度设置
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(15.dp)
|
||||
) {
|
||||
if (player.mediaItemCount > 1) {
|
||||
AssistChip(
|
||||
onClick = {
|
||||
MixDialogBuilder(
|
||||
"选集",
|
||||
colorScheme = playerColorScheme
|
||||
).apply {
|
||||
val indexMap =
|
||||
videos.mapIndexed { index, uri -> index to uri }
|
||||
setContent {
|
||||
SingleSelectItemList(
|
||||
indexMap,
|
||||
currentOption = indexMap[player.currentMediaItemIndex],
|
||||
getLabel = {
|
||||
"${it.first + 1} - ${it.second.fragment}"
|
||||
}
|
||||
) {
|
||||
player.seekToDefaultPosition(it.first)
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
setDefaultNegative("取消")
|
||||
show()
|
||||
}
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "选集",
|
||||
color = Color.White,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
val tracks = player.currentTracks
|
||||
// 2. 音轨 (支持去重)
|
||||
val audioGroups =
|
||||
remember(tracks) { tracks.groups.filter { it.type == C.TRACK_TYPE_AUDIO } }
|
||||
if (audioGroups.size > 1) {
|
||||
PlayerSettingChip("音轨") {
|
||||
val infos = TrackUtils.getFormattedTracks(audioGroups, "音轨")
|
||||
val options = infos.map { it.label }
|
||||
val current =
|
||||
infos.find { it.isSelected }?.label ?: options.firstOrNull() ?: ""
|
||||
|
||||
val currentSpeed = player.playbackParameters.speed
|
||||
|
||||
AssistChip(
|
||||
onClick = {
|
||||
MixDialogBuilder(
|
||||
"播放速度",
|
||||
colorScheme = playerColorScheme
|
||||
).apply {
|
||||
MixDialogBuilder("选择音轨", colorScheme = playerColorScheme).apply {
|
||||
setContent {
|
||||
SingleSelectItemList(
|
||||
listOf(
|
||||
"0.25",
|
||||
"0.5",
|
||||
"0.75",
|
||||
"1.0",
|
||||
"1.25",
|
||||
"1.5",
|
||||
"1.75",
|
||||
"2.0",
|
||||
"2.5",
|
||||
"3.0"
|
||||
), currentOption = "${currentSpeed}"
|
||||
) {
|
||||
player.setPlaybackSpeed(it.toFloat())
|
||||
options,
|
||||
currentOption = current
|
||||
) { selected ->
|
||||
val target = infos.find { it.label == selected }!!
|
||||
player.trackSelectionParameters =
|
||||
player.trackSelectionParameters
|
||||
.buildUpon()
|
||||
.setOverrideForType(
|
||||
TrackSelectionOverride(
|
||||
target.group,
|
||||
target.index
|
||||
)
|
||||
)
|
||||
.build()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
setDefaultNegative("取消")
|
||||
show()
|
||||
}
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "速度: ${currentSpeed}x",
|
||||
color = Color.White,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 字幕选择
|
||||
val textGroups = remember(tracks) {
|
||||
tracks.groups.filter { it.type == C.TRACK_TYPE_TEXT }
|
||||
}
|
||||
if (textGroups.isNotEmpty()) {
|
||||
PlayerSettingChip("字幕") {
|
||||
val infos = TrackUtils.getFormattedTracks(textGroups, "字幕")
|
||||
val options = listOf("关闭") + infos.map { it.label }
|
||||
val current = infos.find { it.isSelected }?.label ?: "关闭"
|
||||
|
||||
MixDialogBuilder("选择字幕", colorScheme = playerColorScheme).apply {
|
||||
setContent {
|
||||
SingleSelectItemList(
|
||||
options,
|
||||
currentOption = current
|
||||
) { selected ->
|
||||
val builder = player.trackSelectionParameters.buildUpon()
|
||||
if (selected == "关闭") {
|
||||
builder.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
|
||||
} else {
|
||||
val target = infos.find { it.label == selected }!!
|
||||
builder.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
|
||||
.setOverrideForType(
|
||||
TrackSelectionOverride(
|
||||
target.group,
|
||||
target.index
|
||||
)
|
||||
)
|
||||
}
|
||||
player.trackSelectionParameters = builder.build()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选集选择
|
||||
if (player.mediaItemCount > 1) {
|
||||
PlayerSettingChip("选集") {
|
||||
val indexMap = videos.mapIndexed { index, uri -> index to uri }
|
||||
MixDialogBuilder("选集", colorScheme = playerColorScheme).apply {
|
||||
setContent {
|
||||
SingleSelectItemList(
|
||||
indexMap,
|
||||
currentOption = indexMap.getOrNull(player.currentMediaItemIndex),
|
||||
getLabel = { "${it.first + 1} - ${it.second.fragment ?: "未命名"}" }
|
||||
) {
|
||||
player.seekToDefaultPosition(it.first)
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 速度选择
|
||||
val currentSpeed = player.playbackParameters.speed
|
||||
|
||||
PlayerSettingChip("速度: ${currentSpeed}x") {
|
||||
MixDialogBuilder("播放速度", colorScheme = playerColorScheme).apply {
|
||||
setContent {
|
||||
val speeds = listOf(
|
||||
"0.25",
|
||||
"0.5",
|
||||
"0.75",
|
||||
"1.0",
|
||||
"1.25",
|
||||
"1.5",
|
||||
"1.75",
|
||||
"2.0",
|
||||
"2.5",
|
||||
"3.0"
|
||||
)
|
||||
SingleSelectItemList(speeds, currentOption = "$currentSpeed") {
|
||||
player.setPlaybackSpeed(it.toFloat())
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.donut.mixfile.activity.video.player
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.TrackGroup
|
||||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.component.common.SingleSelectItemList
|
||||
|
||||
object TrackUtils {
|
||||
/**
|
||||
* 获取格式化后的轨道列表,处理重复 Label
|
||||
*/
|
||||
fun getFormattedTracks(
|
||||
groups: List<Tracks.Group>,
|
||||
defaultPrefix: String = "轨道"
|
||||
): List<TrackInfo> {
|
||||
val options = mutableListOf<TrackInfo>()
|
||||
val labelCounter = mutableMapOf<String, Int>()
|
||||
|
||||
groups.forEach { group ->
|
||||
for (i in 0 until group.length) {
|
||||
val format = group.getTrackFormat(i)
|
||||
val baseLabel = format.label ?: format.language ?: defaultPrefix
|
||||
|
||||
val count = labelCounter.getOrDefault(baseLabel, 0) + 1
|
||||
labelCounter[baseLabel] = count
|
||||
val finalLabel = if (count > 1) "$baseLabel-$count" else baseLabel
|
||||
|
||||
options.add(
|
||||
TrackInfo(
|
||||
finalLabel,
|
||||
group.mediaTrackGroup,
|
||||
i,
|
||||
group.isSelected && group.isTrackSelected(i)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
data class TrackInfo(
|
||||
val label: String,
|
||||
val group: TrackGroup,
|
||||
val index: Int,
|
||||
val isSelected: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示字幕/音轨选择弹窗
|
||||
*/
|
||||
fun showTrackSelector(
|
||||
title: String,
|
||||
player: ExoPlayer,
|
||||
trackType: @C.TrackType Int,
|
||||
colorScheme: ColorScheme,
|
||||
hasDisableOption: Boolean = false
|
||||
) {
|
||||
val groups = player.currentTracks.groups.filter { it.type == trackType }
|
||||
val trackInfos = TrackUtils.getFormattedTracks(
|
||||
groups,
|
||||
if (trackType == C.TRACK_TYPE_TEXT) "字幕" else "音轨"
|
||||
)
|
||||
|
||||
val options =
|
||||
if (hasDisableOption) listOf("关闭") + trackInfos.map { it.label } else trackInfos.map { it.label }
|
||||
val currentLabel =
|
||||
trackInfos.find { it.isSelected }?.label ?: if (hasDisableOption) "关闭" else ""
|
||||
|
||||
MixDialogBuilder(title, colorScheme = colorScheme).apply {
|
||||
setContent {
|
||||
SingleSelectItemList(options, currentOption = currentLabel) { selected ->
|
||||
val builder = player.trackSelectionParameters.buildUpon()
|
||||
if (selected == "关闭") {
|
||||
builder.setTrackTypeDisabled(trackType, true)
|
||||
} else {
|
||||
val info = trackInfos.find { it.label == selected }!!
|
||||
builder.setTrackTypeDisabled(trackType, false)
|
||||
.setOverrideForType(TrackSelectionOverride(info.group, info.index))
|
||||
}
|
||||
player.trackSelectionParameters = builder.build()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
setDefaultNegative("取消")
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PlayerProgressSlider(
|
||||
progress: Float,
|
||||
onSeek: (Float) -> Unit,
|
||||
onSeekFinished: () -> Unit
|
||||
) {
|
||||
Slider(
|
||||
value = progress,
|
||||
onValueChange = onSeek, // 直接执行传入的 seek 逻辑
|
||||
onValueChangeFinished = onSeekFinished,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
thumb = {
|
||||
Box(modifier = Modifier.size(20.dp)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(15.dp)
|
||||
.align(Alignment.Center)
|
||||
.background(Color.White, CircleShape)
|
||||
)
|
||||
}
|
||||
},
|
||||
track = { sliderState ->
|
||||
SliderDefaults.Track(sliderState = sliderState)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlayerSettingChip(label: String, onClick: () -> Unit) {
|
||||
AssistChip(
|
||||
onClick = onClick,
|
||||
label = {
|
||||
Text(
|
||||
text = label,
|
||||
color = Color.White,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -6,9 +6,6 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.donut.mixfile.server.core.Uploader
|
||||
import com.donut.mixfile.server.core.uploaders.A1Uploader
|
||||
import com.donut.mixfile.server.core.uploaders.A2Uploader
|
||||
import com.donut.mixfile.server.core.uploaders.A3Uploader
|
||||
import com.donut.mixfile.server.core.uploaders.base.HttpUploader
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.util.cachedMutableOf
|
||||
|
||||
Reference in New Issue
Block a user