需求描述:
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在 UI 层添加按钮来添加和删除本地音乐文件,首先需要实现几个额外的功能:
- 将用户选择本地音乐的功能集成到应用中。
- 在 UI 层创建按钮,允许用户选择添加音乐文件到播放列表。
- 提供功能来删除播放列表中的音乐文件。
- 集成这些功能到 ViewModel 和 UI 层。
依赖项
dependencies { // Jetpack Compose implementation "androidx.compose.ui:ui:1.1.1" implementation "androidx.compose.material:material:1.1.1" implementation "androidx.compose.ui:ui-tooling-preview:1.1.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" implementation "androidx.activity:activity-compose:1.4.0" implementation "androidx.activity:activity-ktx:1.4.0" // ExoPlayer implementation 'com.google.android.exoplayer:exoplayer:2.14.1' // Room for local database implementation "androidx.room:room-runtime:2.4.2" kapt "androidx.room:room-compiler:2.4.2" implementation "androidx.room:room-ktx:2.4.2" }
数据库和数据实体
如果还没有这样定义,创建一个数据实体类和 Room 数据库来持久化音频文件:
import androidx.room.* tableName = "audio_files") (data class AudioFile( autoGenerate = true) val id: Int = 0, ( name = "file_name") val fileName: String, ( name = "file_uri") val fileUri: String () interface AudioFileDao { "SELECT * FROM audio_files") ( fun getAll(): List<AudioFile> onConflict = OnConflictStrategy.REPLACE) ( fun insert(audioFile: AudioFile) fun delete(audioFile: AudioFile) } entities = [AudioFile::class], version = 1) (abstract class AppDatabase : RoomDatabase() { abstract fun audioFileDao(): AudioFileDao }
更新 ViewModel
更新你的 ViewModel 来添加和删除音频文件:
import android.app.Application import android.content.Context import android.net.Uri import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import androidx.room.Room import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class AudioPlayerViewModel(application: Application) : AndroidViewModel(application) { private val context: Context = application.applicationContext private val exoPlayer: ExoPlayer = ExoPlayer.Builder(application.applicationContext).build() private val db: AppDatabase = Room.databaseBuilder( application.applicationContext, AppDatabase::class.java, "audio_db" ).build() private val audioFileDao = db.audioFileDao() private val _audioFiles = mutableListOf<AudioFile>() val audioFiles: List<AudioFile> get() = _audioFiles init { loadAudioFiles() } private fun loadAudioFiles() { viewModelScope.launch(Dispatchers.IO) { val audioFilesFromDb = audioFileDao.getAll() _audioFiles.clear() _audioFiles.addAll(audioFilesFromDb) } } fun addAudioFile(fileName: String, fileUri: String) { viewModelScope.launch(Dispatchers.IO) { val audioFile = AudioFile(fileName = fileName, fileUri = fileUri) audioFileDao.insert(audioFile) _audioFiles.add(audioFile) } } fun deleteAudioFile(audioFile: AudioFile) { viewModelScope.launch(Dispatchers.IO) { audioFileDao.delete(audioFile) _audioFiles.remove(audioFile) } } fun playAudio(audioFile: AudioFile) { val uri = Uri.parse(audioFile.fileUri) val mediaItem = MediaItem.fromUri(uri) exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.playWhenReady = true } override fun onCleared() { super.onCleared() exoPlayer.release() } }
UI层
支持添加、删除音乐, 以及播放功能
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApp() } } } fun MyApp(audioPlayerViewModel: AudioPlayerViewModel = viewModel()) { var newFileName by remember { mutableStateOf("") } val audioFiles by rememberUpdatedState(audioPlayerViewModel.audioFiles) val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent(), onResult = { uri -> uri?.let { audioPlayerViewModel.addAudioFile(newFileName, it.toString()) newFileName = "" } } ) Scaffold( topBar = { TopAppBar(title = { Text("Audio Player") }) }, content = { Column( Modifier .fillMaxSize() .padding(16.dp) ) { OutlinedTextField( value = newFileName, onValueChange = { newFileName = it }, label = { Text("New Audio File Name") } ) Spacer(modifier = Modifier.height(8.dp)) Button( onClick = { if (newFileName.isNotEmpty()) { launcher.launch("audio/*") } }, modifier = Modifier.fillMaxWidth() ) { Text("Add to Playlist") } Spacer(modifier = Modifier.height(16.dp)) LazyColumn { items(audioFiles) { audioFile -> Row( modifier = Modifier .fillMaxWidth() .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Column { Text( text = "File: ${audioFile.fileName}", fontSize = 20.sp ) Text( text = "URI: ${audioFile.fileUri}", fontSize = 12.sp ) } Row { IconButton(onClick = { audioPlayerViewModel.playAudio(audioFile) }) { Icon( imageVector = Icons.Default.PlayArrow, contentDescription = "Play" ) } IconButton(onClick = { audioPlayerViewModel.deleteAudioFile(audioFile) }) { Icon( imageVector = Icons.Default.Delete, contentDescription = "Delete" ) } } } } } Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { if (audioFiles.isNotEmpty()) { audioPlayerViewModel.playAudio(audioFiles.random()) } }, modifier = Modifier.fillMaxWidth() ) { Text("Play Random Audio") } } } ) }
解释
- 依赖项:
- 在 build.gradle 文件中包括 ExoPlayer 和 Room 相关的依赖项。
- 数据库和数据实体:
- 创建 AudioFile 数据实体类,并包括 id、fileName 和 fileUri 字段。
- 在 AppDatabase 中包含 AudioFileDao,并实现基本的增删查操作。
- ViewModel:
- AudioPlayerViewModel 类管理每个 ExoPlayer 实例,并实现播放逻辑。
- addAudioFile 和 deleteAudioFile 方法分别用于添加和删除播放列表中的音频文件。
- playAudio 方法根据音频文件名创建媒体项,然后通过 ExoPlayer 播放。
- loadAudioFiles 方法从数据库加载持久化的播放列表。
- UI 层:
- OutlinedTextField 用于输入新的音频文件名。
- Button 控件用于打开内容选择器,让用户选择本地音频文件。
- 使用 LazyColumn 显示播放列表。
- 对于每个播放列表项,提供播放和删除按钮。
- 添加一个按钮来随机播放列表中的音频文件。
这样,在 UI 层,可以通过按钮选择本地音频文件并将其添加到播放列表,或者从播放列表中删除。ExoPlayer 的逻辑集中在 ViewModel 中,与 UI 层完全解耦,同时通过 Room 数据库实现播放列表的持久化存储