安卓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 数据库实现播放列表的持久化存储



相关文章
|
5月前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。
|
1月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
38 6
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
69 8
|
2月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
54 4
|
3月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
25 0
Android 利用MediaPlayer实现音乐播放
|
3月前
|
编解码 网络协议 开发工具
Android平台如何实现多路低延迟RTSP|RTMP播放?
本文档详细介绍了大牛直播SDK在Android平台上实现RTSP与RTMP流媒体播放及录像功能的技术细节。早在2015年,SDK的第一版就已经支持了多实例播放,并且通过简单的实例封装就能轻松实现。文档中提供了代码示例,展示了如何开启播放、停止播放以及开始和停止录像等功能。此外,SDK还提供了丰富的配置选项,例如设置录像目录、文件大小限制、转码选项等。总结部分列出了该SDK的关键特性,包括但不限于高稳定性和低延迟的播放能力、多实例支持、事件回调、硬解码支持、网络状态监控以及复杂的网络环境处理等。这些功能使得SDK能够应对各种应用场景,特别是在对延迟和稳定性有极高要求的情况下表现优异。
|
3月前
|
编解码 网络协议 vr&ar
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
这段内容讲述了VR头显中实现高分辨率视频播放的技术背景与实现方法,并强调了其重要性。高分辨率对于提升VR体验至关重要,它能提供更清晰的画面、增强沉浸感、补偿透镜放大效应,并维持宽广视场角下的图像质量。文中提到的大牛直播SDK具备极低的延迟(200-400ms),支持多种协议与格式,并具有丰富的功能特性,如多实例播放、事件回调、视频及音频格式支持等。此外,提供了基于Unity的播放器示例代码,展示了如何配置播放参数并开始播放。最后,作者指出此类技术在远程控制、虚拟仿真等应用场景中的重要意义。
|
4月前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
65 4
|
4月前
|
存储 移动开发 Android开发
使用kotlin Jetpack Compose框架开发安卓app, webview中h5如何访问手机存储上传文件
在Kotlin和Jetpack Compose中,集成WebView以支持HTML5页面访问手机存储及上传音频文件涉及关键步骤:1) 添加`READ_EXTERNAL_STORAGE`和`WRITE_EXTERNAL_STORAGE`权限,考虑Android 11的分区存储;2) 配置WebView允许JavaScript和文件访问,启用`javaScriptEnabled`、`allowFileAccess`等设置;3) HTML5页面使用`<input type="file">`让用户选择文件,利用File API;
|
5月前
|
Android开发
Jetpack Compose: Hello Android
Jetpack Compose: Hello Android