安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐

简介: 为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤:1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。2. **创建UI按钮**:在界面中创建添加和删除按钮。3. **数据库功能**:使用Room数据库来存储音频文件信息。4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。

需求描述:

安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐



为了在 UI 层添加按钮来添加和删除本地音乐文件,首先需要实现几个额外的功能:

  1. 将用户选择本地音乐的功能集成到应用中。
  2. 在 UI 层创建按钮,允许用户选择添加音乐文件到播放列表。
  3. 提供功能来删除播放列表中的音乐文件。
  4. 集成这些功能到 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.*

@Entity(tableName = "audio_files")
data class AudioFile(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "file_name") val fileName: String,
    @ColumnInfo(name = "file_uri") val fileUri: String
)

@Dao
interface AudioFileDao {
    @Query("SELECT * FROM audio_files")
    fun getAll(): List<AudioFile>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(audioFile: AudioFile)

    @Delete
    fun delete(audioFile: AudioFile)
}

@Database(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()
        }
    }
}

@Composable
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")
                }
            }
        }
    )
}



解释

  1. 依赖项:
  • 在 build.gradle 文件中包括 ExoPlayer 和 Room 相关的依赖项。
  1. 数据库和数据实体:
  • 创建 AudioFile 数据实体类,并包括 id、fileName 和 fileUri 字段。
  • 在 AppDatabase 中包含 AudioFileDao,并实现基本的增删查操作。
  1. ViewModel:
  • AudioPlayerViewModel 类管理每个 ExoPlayer 实例,并实现播放逻辑。
  • addAudioFile 和 deleteAudioFile 方法分别用于添加和删除播放列表中的音频文件。
  • playAudio 方法根据音频文件名创建媒体项,然后通过 ExoPlayer 播放。
  • loadAudioFiles 方法从数据库加载持久化的播放列表。
  1. UI 层:
  • OutlinedTextField 用于输入新的音频文件名。
  • Button 控件用于打开内容选择器,让用户选择本地音频文件。
  • 使用 LazyColumn 显示播放列表。
  • 对于每个播放列表项,提供播放和删除按钮。
  • 添加一个按钮来随机播放列表中的音频文件。

这样,在 UI 层,可以通过按钮选择本地音频文件并将其添加到播放列表,或者从播放列表中删除。ExoPlayer 的逻辑集中在 ViewModel 中,与 UI 层完全解耦,同时通过 Room 数据库实现播放列表的持久化存储



相关文章
|
29天前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。
|
1月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
1月前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
1月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
25天前
|
安全 Java API
Android获取Wi-Fi网络列表
【6月更文挑战第21天】
|
1月前
|
安全 网络安全 API
kotlin安卓开发JetPack Compose 如何使用webview 打开网页时给webview注入cookie
在Jetpack Compose中使用WebView需借助AndroidView。要注入Cookie,首先在`build.gradle`添加WebView依赖,如`androidx.webkit:webkit:1.4.0`。接着创建自定义`ComposableWebView`,通过`CookieManager`设置接受第三方Cookie并注入Cookie字符串。最后在Compose界面使用这个自定义组件加载URL。注意Android 9及以上版本可能需要在网络安全配置中允许第三方Cookie。
179 0
|
2月前
|
移动开发 数据库 Android开发
不止 Android!Compose Multiplatform 来了
不止 Android!Compose Multiplatform 来了
129 0
|
9天前
|
开发工具 Android开发 iOS开发
探索Android与iOS开发的差异与挑战
【7月更文挑战第11天】在移动应用开发的广阔天地中,Android和iOS两大平台如同双子星座般耀眼,各自拥有独特的开发生态和用户群体。本文将深入分析这两个平台的显著差异,从技术架构到开发工具,再到市场定位,揭示它们之间的异同。通过比较,我们不仅能够更好地理解各自的优势和局限,还能洞察未来移动应用开发的趋势。
|
6天前
|
Android开发 Kotlin
kotlin开发安卓app,如何让布局自适应系统传统导航和全面屏导航
使用`navigationBarsPadding()`修饰符实现界面自适应,自动处理底部导航栏的内边距,再加上`.padding(bottom = 10.dp)`设定内容与屏幕底部的距离,以完成全面的布局适配。示例代码采用Kotlin。
40 15
|
3天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异性与互操作性
【7月更文挑战第17天】在移动应用开发的广阔天地中,安卓和iOS这两大操作系统如同双子星座般璀璨夺目。它们各自拥有独特的开发环境、编程语言和用户群体,为开发者提供了不同的挑战和机遇。本文将从多个维度深入剖析安卓与iOS开发的差异性,并探讨它们之间的互操作性如何实现,以期为开发者们提供一份实用的指南。
16 7

热门文章

最新文章