You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ExoPlayer2中预加载无URL播放列表并从API更新当前MediaItem?

最优解决方案:ExoPlayer播放列表懒加载音频URL

你的核心痛点是一次性请求所有音乐详情导致播放列表加载过慢,最优方案是懒加载音频URL——先基于播放列表API的基础信息构建占位播放列表,在播放到对应歌曲时再异步获取音频链接并更新,同时可以提前预加载下一首的链接,兼顾加载速度和播放流畅度。

具体实现步骤

1. 构建初始占位播放列表

拿到播放列表API返回的ID、标题、图片后,直接创建不含音频Uri的MediaItem,快速初始化ExoPlayer的播放列表:

// 假设apiResponse是播放列表接口返回的数据列表
val initialPlaylist = apiResponse.map { song ->
    MediaItem.Builder()
        .setMediaId(song.id) // 用歌曲ID作为MediaItem的唯一标识
        .setMediaMetadata(
            MediaMetadata.Builder()
                .setTitle(song.title)
                .setArtworkUri(Uri.parse(song.imageUrl))
                .build()
        )
        // 暂不设置音频Uri,留空
        .build()
}

player.setMediaItems(initialPlaylist)
player.prepare()

2. 监听播放事件,按需加载音频URL

通过ExoPlayer的Player.Listener监听媒体项切换事件,当即将播放某首歌时,检查是否已有音频Uri,若无则异步请求详情API并更新播放列表:

// 用HashMap缓存已获取的音频URL,避免重复请求
private val audioUrlCache = HashMap<String, String>()

player.addListener(object : Player.Listener {
    override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
        mediaItem ?: return
        
        // 先查缓存,没有再请求API
        val cachedUrl = audioUrlCache[mediaItem.mediaId]
        if (cachedUrl != null) {
            updateMediaItemWithUrl(mediaItem, cachedUrl)
            return
        }

        // 无缓存,异步请求详情API
        lifecycleScope.launch {
            runCatching {
                val songDetail = musicDetailApi.getSongDetail(mediaItem.mediaId)
                val audioUrl = songDetail.audioUrl
                // 存入缓存
                audioUrlCache[mediaItem.mediaId] = audioUrl
                // 更新当前MediaItem的Uri
                updateMediaItemWithUrl(mediaItem, audioUrl)
                // 如果播放器处于准备播放状态,恢复播放
                if (player.playWhenReady) {
                    player.play()
                }
            }.onFailure {
                // 请求失败处理:比如提示用户、自动跳转到下一首
                player.seekToNext()
                Toast.makeText(context, "加载音频失败,跳过当前歌曲", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun updateMediaItemWithUrl(mediaItem: MediaItem, audioUrl: String) {
        val currentIndex = player.currentMediaItemIndex
        // 构建更新后的MediaItem
        val updatedItem = mediaItem.buildUpon()
            .setUri(Uri.parse(audioUrl))
            .build()
        // 替换播放列表中的对应项
        player.removeMediaItem(currentIndex)
        player.addMediaItem(currentIndex, updatedItem)
        // 重新准备播放器,加载新的音频源
        player.prepare()
    }
})

3. 提前预加载下一首,优化播放流畅度

为了避免切换歌曲时的等待,可以在当前歌曲播放过程中,提前请求下一首的音频URL:

player.addListener(object : Player.Listener {
    override fun onPositionDiscontinuity(oldPos: Player.PositionInfo, newPos: Player.PositionInfo, reason: Int) {
        val nextIndex = (player.currentMediaItemIndex + 1) % player.mediaItemCount
        val nextMediaItem = player.getMediaItemAt(nextIndex)
        
        if (audioUrlCache[nextMediaItem.mediaId] == null) {
            lifecycleScope.launch {
                runCatching {
                    val songDetail = musicDetailApi.getSongDetail(nextMediaItem.mediaId)
                    audioUrlCache[nextMediaItem.mediaId] = songDetail.audioUrl
                    // 提前更新下一首的MediaItem
                    val updatedItem = nextMediaItem.buildUpon()
                        .setUri(Uri.parse(songDetail.audioUrl))
                        .build()
                    player.removeMediaItem(nextIndex)
                    player.addMediaItem(nextIndex, updatedItem)
                }
            }
        }
    }
})

额外优化建议

  • 缓存策略:可以将音频URL缓存到本地(比如SharedPreferences或Room),下次打开APP时直接复用,减少网络请求。
  • 加载状态UI:在请求音频URL期间,显示加载动画或进度条,让用户明确当前状态。
  • 重试机制:如果API请求失败,可以增加1-2次重试逻辑,提升成功率。

内容的提问来源于stack exchange,提问作者AP Jake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 22:27:23