安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【本地】音频,播放完随机播放下一首,遇到播放错误,也自动播放下一首

简介: 使用Kotlin和Jetpack Compose开发的安卓应用中,实现了两个EvoPlayer同时播放res/raw目录下的音频。一个音轨播放人声(顺序播放),另一个播放背景音乐(随机播放)。每个音轨都有独立的播放和停止控制,且在播放结束或遇到错误时会自动切换到下一首。MediaPlayer置于ViewModel中,UI界面包含播放和停止按钮,控制两个音轨。每次切换音频前,还会随机调整播放速度在0.9到1.2之间。代码示例展示了如何创建ViewModel和UI以实现这一功能。



需求描述:

kotlin开发安卓app, Jetpack Compose框架,使用2个EvoPlayer播放res/raw中的音乐,实现2个音轨同时播放,一个是人声音轨,有多个人声音频,另一个是背景音乐音轨,有多个背景音乐, 不断循环播放列表中的音乐, 人声轨道为顺序播放,背景音乐轨道为随机播放, MediaPlayer在viewModel中, UI层有2个按钮,分别控制播放和停止,按钮同时控制2个音轨的播放和停止 。 遇到报错,则继续播放。人声轨道和背景音乐轨道,每次播放下一首音频之前,随机设置播放速度在0.9到1.2之间。






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'
}


接下来,我们创建ViewModel来管理ExoPlayer:


import android.app.Application
import android.content.Context
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.PlaybackParameters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.random.Random

class AudioPlayerViewModel(application: Application) : AndroidViewModel(application) {

    private val context: Context = application.applicationContext

    private val voicePlayer: ExoPlayer = ExoPlayer.Builder(context).build().apply {
        addListener(object: Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                if (playbackState == Player.STATE_ENDED) {
                    playNextVoiceTrack()
                }
            }

            override fun onPlayerError(error: PlaybackException) {
                playNextVoiceTrack()
            }
        })
    }

    private val bgmPlayer: ExoPlayer = ExoPlayer.Builder(context).build().apply {
        addListener(object: Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                if (playbackState == Player.STATE_ENDED) {
                    playNextBgmTrack()
                }
            }

            override fun onPlayerError(error: PlaybackException) {
                playNextBgmTrack()
            }
        })
    }

    private val voiceTracks = listOf(R.raw.voice1, R.raw.voice2) // Add your voice tracks here
    private val bgmTracks = listOf(R.raw.bgm1, R.raw.bgm2) // Add your BGM tracks here

    private var currentVoiceTrackIndex = 0

    init {
        playNextVoiceTrack()
        playNextBgmTrack()
    }

    private fun playAudio(player: ExoPlayer, uri: Uri, playbackSpeed: Float) {
        player.apply {
            val mediaItem = MediaItem.fromUri(uri)
            setMediaItem(mediaItem)
            playbackParameters = PlaybackParameters(playbackSpeed)
            prepare()
            playWhenReady = true
        }
    }

    private fun playNextVoiceTrack() {
        val uri = getUriForRawFile(context, voiceTracks[currentVoiceTrackIndex])
        val playbackSpeed = Random.nextFloat() * (1.2f - 0.9f) + 0.9f // Random speed between 0.9 and 1.2
        playAudio(voicePlayer, uri, playbackSpeed)
        currentVoiceTrackIndex = (currentVoiceTrackIndex + 1) % voiceTracks.size
    }

    private fun playNextBgmTrack() {
        val randomIndex = Random.nextInt(bgmTracks.size)
        val uri = getUriForRawFile(context, bgmTracks[randomIndex])
        val playbackSpeed = Random.nextFloat() * (1.2f - 0.9f) + 0.9f // Random speed between 0.9 and 1.2
        playAudio(bgmPlayer, uri, playbackSpeed)
    }

    private fun getUriForRawFile(context: Context, resId: Int): Uri {
        return Uri.parse("android.resource://${context.packageName}/$resId")
    }

    fun play() {
        voicePlayer.playWhenReady = true
        bgmPlayer.playWhenReady = true
    }

    fun stop() {
        voicePlayer.stop()
        bgmPlayer.stop()
    }

    override fun onCleared() {
        super.onCleared()
        voicePlayer.release()
        bgmPlayer.release()
    }
}



接下来,我们在 UI 层创建 Compose 组件,使用按钮来控制两个音轨的播放和停止。




import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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()) {
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Audio Player") })
        },
        content = {
            Column(
                Modifier
                    .fillMaxSize()
                    .padding(16.dp),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally
            ) {
                Button(
                    onClick = { audioPlayerViewModel.play() },
                    modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
                ) {
                    Text("Play Audio")
                }
                Button(
                    onClick = { audioPlayerViewModel.stop() },
                    modifier = Modifier.fillMaxWidth()
                ) {
                    Text("Stop Audio")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}


解释

  1. ViewModel:
  • AudioPlayerViewModel 中初始化两个 ExoPlayer 实例: voicePlayer 和 bgmPlayer。
  • 添加了两个 Player.Listener 来监听播放状态和错误,分别为人声音轨和背景音乐音轨处理播放结束和错误事件。
  • 提供 playNextVoiceTrack 和 playNextBgmTrack 方法来实现分别播放人声音轨和背景音乐音轨,并在每次播放时随机设置播放速度。
  • playAudio 方法用来播放指定 URI 的音轨,并设置播放速度。
  • play 方法和 stop 方法分别控制两个音轨的播放和停止。
  1. UI层:
  • MyApp 组件使用了两个按钮,通过点击按钮来控制 viewModel 中的播放和停止操作。

通过这种方式,应用程序可以同时播放两个音轨,并分别控制播放和停止。人声音轨按照列表顺序播放,背景音乐音轨随机播放。每次播放下一首音轨之前,都会随机设置播放速度。错误处理逻辑保证了在出现错误时可以继续播放下一首音轨。




相关文章
|
4月前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。
|
24天前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
34 4
|
2月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
15 0
Android 利用MediaPlayer实现音乐播放
|
2月前
|
编解码 网络协议 开发工具
Android平台如何实现多路低延迟RTSP|RTMP播放?
本文档详细介绍了大牛直播SDK在Android平台上实现RTSP与RTMP流媒体播放及录像功能的技术细节。早在2015年,SDK的第一版就已经支持了多实例播放,并且通过简单的实例封装就能轻松实现。文档中提供了代码示例,展示了如何开启播放、停止播放以及开始和停止录像等功能。此外,SDK还提供了丰富的配置选项,例如设置录像目录、文件大小限制、转码选项等。总结部分列出了该SDK的关键特性,包括但不限于高稳定性和低延迟的播放能力、多实例支持、事件回调、硬解码支持、网络状态监控以及复杂的网络环境处理等。这些功能使得SDK能够应对各种应用场景,特别是在对延迟和稳定性有极高要求的情况下表现优异。
|
2月前
|
编解码 网络协议 vr&ar
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
这段内容讲述了VR头显中实现高分辨率视频播放的技术背景与实现方法,并强调了其重要性。高分辨率对于提升VR体验至关重要,它能提供更清晰的画面、增强沉浸感、补偿透镜放大效应,并维持宽广视场角下的图像质量。文中提到的大牛直播SDK具备极低的延迟(200-400ms),支持多种协议与格式,并具有丰富的功能特性,如多实例播放、事件回调、视频及音频格式支持等。此外,提供了基于Unity的播放器示例代码,展示了如何配置播放参数并开始播放。最后,作者指出此类技术在远程控制、虚拟仿真等应用场景中的重要意义。
|
3月前
|
存储 移动开发 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;
|
4月前
深入了解 Jetpack Compose 中的 Modifier
深入了解 Jetpack Compose 中的 Modifier
|
4月前
|
Android开发
Jetpack Compose: Hello Android
Jetpack Compose: Hello Android
|
20天前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
42 1
|
2月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
40 4
下一篇
无影云桌面