安卓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
SpringBoot使用云端资源url下载文件的接口写法
在Spring Boot中实现从云端资源URL下载文件的功能可通过定义REST接口完成。示例代码展示了一个`FileDownloadController`,它包含使用`@GetMapping`注解的方法`downloadFile`,此方法接收URL参数,利用`RestTemplate`下载文件,并将文件字节数组封装为`ByteArrayResource`返回给客户端。此外,通过设置HTTP响应头,确保文件以附件形式下载。这种方法适用于从AWS S3或Google Cloud Storage等云服务下载文件。
450 7
|
1天前
|
安全 API 网络安全
使用OkHttp进行HTTPS请求的Kotlin实现
使用OkHttp进行HTTPS请求的Kotlin实现
|
3月前
|
Java
Java通过HttpClient从外部url下载文件到本地
该Java程序旨在通过URL将外部网络文件(如图片)下载至本地,并解决防盗链问题。首先,它通过`HttpGet`请求获取远程文件,并通过设置`Referer`头防止防盗链。然后,根据响应内容类型确定文件后缀并保存至指定路径。测试表明,程序能够成功下载文件。
486 8
Java通过HttpClient从外部url下载文件到本地
|
2月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
58 4
|
3月前
|
存储 API 网络架构
【Azure 存储服务】MP4视频放在Azure的Blob里面,用生成URL在浏览器中打开之后,视频可以正常播放却无法拖拽视频的进度
【Azure 存储服务】MP4视频放在Azure的Blob里面,用生成URL在浏览器中打开之后,视频可以正常播放却无法拖拽视频的进度
|
3月前
|
开发工具 git
IDEA中怎么使用git下载项目到本地,通过URL克隆项目(giteegithub)
IDEA中怎么使用git下载项目到本地,通过URL克隆项目(giteegithub)
252 0
|
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;
|
4月前
|
JavaScript
js 下载文件(根据URL下载文件,根据URL下载图片)
js 下载文件(根据URL下载文件,根据URL下载图片)
549 0
|
5月前
深入了解 Jetpack Compose 中的 Modifier
深入了解 Jetpack Compose 中的 Modifier
|
6月前
|
存储 安全 Android开发
构建高效的Android应用:Kotlin与Jetpack的结合
【5月更文挑战第31天】 在移动开发的世界中,Android 平台因其开放性和广泛的用户基础而备受开发者青睐。随着技术的进步和用户需求的不断升级,开发一个高效、流畅且易于维护的 Android 应用变得愈发重要。本文将探讨如何通过结合现代编程语言 Kotlin 和 Android Jetpack 组件来提升 Android 应用的性能和可维护性。我们将深入分析 Kotlin 语言的优势,探索 Jetpack 组件的核心功能,并通过实例演示如何在实际项目中应用这些技术。

热门文章

最新文章