安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首

简介: 这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。

需求描述:


kotlin 开发安卓app, 使用Jetpack Compose框架。 功能描述: 要播放一个列表中的音频,里面存储的是远程url音频。 使用ExoPlayer进行播放,  搭配OKhttp库,如果已经下载过该文件且文件大小与url返回的文件大小一致,则使用本地缓存的文件, 如果没有下载过,或者文件大小不一致,则先下载覆盖本地的文件, 下载完再播放, 如果本地文件不存在,直接下载然后播放。 帮我写一段代码示例。  分为viewModel和UI层, UI层 控制播放和停止。 音频播放完成时,随机播放下一首。 如果网络请求异常,也随机播放下一首。 如果播放出现异常, 也随机播放下一首。 播放下一首之前,随机设置播放速度为0.9到1.2之间




下面是一整套 Kotlin 代码示例,用于实现你描述的功能。这个示例使用 Jetpack Compose 和 ExoPlayer 来播放音频列表,并使用 OkHttp 库来处理音频文件的下载和缓存验证。音频播放完成或出现异常时,将自动随机播放下一首音频,并设置随机播放速度(0.9 到 1.2 之间)。

依赖项

首先,确保在 build.gradle 文件中添加必要的依赖项:



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'

    // OkHttp for downloading files
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'

    // Coroutines for background tasks
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}



ViewModel

首先,我们创建 AudioPlayerViewModel 来管理 ExoPlayer 实例、音频文件下载和缓存验证逻辑。

AudioPlayerViewModel.kt


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.PlaybackParameters
import com.google.android.exoplayer2.Player
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileOutputStream
import kotlin.random.Random

class AudioPlayerViewModel(application: Application) : AndroidViewModel(application) {
    private val context: Context = application.applicationContext
    private val exoPlayer: ExoPlayer = ExoPlayer.Builder(context).build()
    private val okHttpClient = OkHttpClient()

    private val audioUrls = listOf(
        "https://example.com/path/to/audio1.mp3",
        "https://example.com/path/to/audio2.mp3",
        "https://example.com/path/to/audio3.mp3"
    )

    init {
        exoPlayer.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                if (playbackState == Player.STATE_ENDED) {
                    playRandomAudio()
                }
            }

            override fun onPlayerError(error: com.google.android.exoplayer2.PlaybackException) {
                playRandomAudio()
            }
        })
    }

    fun play() {
        playRandomAudio()
    }

    fun stop() {
        exoPlayer.stop()
    }

    private fun playRandomAudio() {
        val url = audioUrls.random()
        viewModelScope.launch {
            try {
                val file = getCachedFile(url)

                if (isCacheValid(url, file)) {
                    playAudio(file)
                } else {
                    downloadAndPlay(url, file)
                }
            } catch (e: Exception) {
                e.printStackTrace()
                playRandomAudio() // Play next audio on error
            }
        }
    }

    private suspend fun getCachedFile(url: String): File {
        return withContext(Dispatchers.IO) {
            val fileName = url.substringAfterLast("/")
            File(context.cacheDir, fileName)
        }
    }

    private suspend fun isCacheValid(url: String, file: File): Boolean {
        return withContext(Dispatchers.IO) {
            if (!file.exists()) {
                return@withContext false
            }

            val request = Request.Builder().url(url).head().build()
            val response = okHttpClient.newCall(request).execute()

            if (!response.isSuccessful) {
                return@withContext false
            }

            val remoteFileSize = response.header("Content-Length")?.toLongOrNull() ?: return@withContext false
            val localFileSize = file.length()

            remoteFileSize == localFileSize
        }
    }

    private suspend fun downloadAndPlay(url: String, file: File) {
        withContext(Dispatchers.IO) {
            try {
                val request = Request.Builder().url(url).build()
                val response = okHttpClient.newCall(request).execute()

                if (response.isSuccessful) {
                    response.body?.let { body ->
                        val inputStream = body.byteStream()
                        val outputStream = FileOutputStream(file)

                        inputStream.use { input ->
                            outputStream.use { output ->
                                input.copyTo(output)
                            }
                        }

                        playAudio(file)
                    }
                } else {
                    playRandomAudio() // Play next audio on error
                }
            } catch (e: Exception) {
                e.printStackTrace()
                playRandomAudio() // Play next audio on error
            }
        }
    }

    private fun playAudio(file: File) {
        val uri = Uri.fromFile(file)
        val mediaItem = MediaItem.fromUri(uri)
        val playbackSpeed = Random.nextFloat() * (1.2f - 0.9f) + 0.9f // Random speed between 0.9 and 1.2

        exoPlayer.setMediaItem(mediaItem)
        exoPlayer.prepare()
        exoPlayer.playWhenReady = true
        exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed)
    }

    override fun onCleared() {
        super.onCleared()
        exoPlayer.release()
    }
}




UI层

接下来,为 UI 层创建 Compose 组件,以控制播放和停止。

MainActivity.kt



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




解释

ViewModel

  1. 依赖注入:通过构造函数注入 Application 以获取 context。
  2. ExoPlayer 实例:在 AudioPlayerViewModel 中初始化 ExoPlayer 实例。
  3. OkHttp 客户端:在 AudioPlayerViewModel 中初始化 OkHttpClient 实例。
  4. 播放逻辑:
  • play 方法:调用 playRandomAudio 随机播放一首音频。
  • playRandomAudio 方法:随机选择一个 URL,检查缓存文件是否已存在且有效(文件大小一致)。如果缓存有效,则直接播放本地文件;否则下载并缓存,然后播放。如果播放出现错误,则随机播放下一首。
  • isCacheValid 方法:发送 HEAD 请求获取远程文件大小,并与本地文件大小进行比较,以验证缓存是否有效。
  • downloadAndPlay 方法:如果缓存无效,下载音频文件并缓存到指定文件夹中,然后播放音频。如果下载出现错误,则随机播放下一首。
  • playAudio 方法:通过 ExoPlayer 播放本地文件,并设置随机播放速度(0.9 到 1.2 之间)。
  1. ExoPlayer 事件监听:
  • onPlaybackStateChanged 方法:监听播放状态,当播放结束时,随机播放下一首音频。
  • onPlayerError 方法:监听播放错误,当发生错误时,随机播放下一首音频。

UI层

  1. Compose 布局:通过 Column 布局组织各个 UI 组件,包括播放和停止按钮。
  2. 按钮操作:
  • Play Audio 按钮:调用 audioPlayerViewModel.play() 开始播放音频。
  • Stop Audio 按钮:调用 audioPlayerViewModel.stop() 停止音频播放。

通过以上代码,你可以随机播放远程 URL 列表中的音频文件,并使用 OkHttp 下载和缓存音频文件。如果文件已经下载且大小一致,则直接从缓存中播放,避免重复下载。用户界面使用 Jetpack Compose 构建,并提供播放和停止按钮以控制音频播放,同时处理播放完成或出现错误时自动随机播放下一首音频,并设置随机播放速度。





相关文章
|
4月前
|
安全 Java Android开发
探索安卓开发的未来:Kotlin语言的崛起与挑战
在这篇文章中,我们将深入探讨Kotlin语言在安卓开发领域的应用及其对传统Java开发的颠覆性影响。通过分析Kotlin的特性、社区支持以及在实际项目中的应用案例,我们揭示了这一现代编程语言如何为开发者提供更简洁、更安全的编程体验,并讨论了它在面对性能优化和向后兼容性时所面临的挑战。文章旨在为读者呈现一个全面的视角,评估Kotlin作为未来安卓开发主流语言的可能性。
74 1
|
1月前
|
安全 API 网络安全
使用OkHttp进行HTTPS请求的Kotlin实现
使用OkHttp进行HTTPS请求的Kotlin实现
|
3月前
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
24 1
|
3月前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
84 6
|
3月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
82 4
|
4月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
32 0
Android 利用MediaPlayer实现音乐播放
|
4月前
|
编解码 网络协议 开发工具
Android平台如何实现多路低延迟RTSP|RTMP播放?
本文档详细介绍了大牛直播SDK在Android平台上实现RTSP与RTMP流媒体播放及录像功能的技术细节。早在2015年,SDK的第一版就已经支持了多实例播放,并且通过简单的实例封装就能轻松实现。文档中提供了代码示例,展示了如何开启播放、停止播放以及开始和停止录像等功能。此外,SDK还提供了丰富的配置选项,例如设置录像目录、文件大小限制、转码选项等。总结部分列出了该SDK的关键特性,包括但不限于高稳定性和低延迟的播放能力、多实例支持、事件回调、硬解码支持、网络状态监控以及复杂的网络环境处理等。这些功能使得SDK能够应对各种应用场景,特别是在对延迟和稳定性有极高要求的情况下表现优异。
103 5
|
4月前
|
编解码 网络协议 vr&ar
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
这段内容讲述了VR头显中实现高分辨率视频播放的技术背景与实现方法,并强调了其重要性。高分辨率对于提升VR体验至关重要,它能提供更清晰的画面、增强沉浸感、补偿透镜放大效应,并维持宽广视场角下的图像质量。文中提到的大牛直播SDK具备极低的延迟(200-400ms),支持多种协议与格式,并具有丰富的功能特性,如多实例播放、事件回调、视频及音频格式支持等。此外,提供了基于Unity的播放器示例代码,展示了如何配置播放参数并开始播放。最后,作者指出此类技术在远程控制、虚拟仿真等应用场景中的重要意义。
|
3月前
|
调度 Android开发 开发者
探索安卓开发中的新技术:Kotlin协程
【9月更文挑战第9天】本文将深入探讨Kotlin协程在安卓开发中的应用,揭示其如何优雅地处理异步任务。我们将从基础概念入手,逐步深入到实际开发场景,最后通过代码示例直观展示协程的魔力。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往更高效、更简洁代码的大门。
|
4月前
|
移动开发 Java Android开发
探索安卓应用开发的新趋势:Kotlin与Coroutines
【8月更文挑战第31天】本文旨在为读者揭示安卓开发中的最新潮流,特别是Kotlin语言及其协程特性如何重塑移动应用的构建方式。我们将通过实际代码示例深入理解Kotlin简洁语法背后的强大功能,并探讨如何利用Coroutines优雅地处理异步任务,从而提升应用性能和用户体验。无论你是安卓开发的新手还是资深开发者,这篇文章都将为你带来新的启示和灵感。

热门文章

最新文章