如何在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
相关产品推荐
相关产品推荐

