如何先从URL批量下载音频文件再依次播放?
最优方案:批量下载音频后依次播放(Swift实现)
我来帮你搞定这个需求!关键在于先确保所有音频文件都下载完成,再启动有序的播放队列,这样就能避免下载和播放并行导致的混乱。下面是具体的实现步骤和完整代码:
一、核心思路拆解
- 先整理好你服务器上的所有音频URL列表,用
DispatchGroup跟踪每个文件的下载状态 - 等所有文件都下载到本地文档目录后,收集本地文件路径
- 利用
AVAudioPlayer逐个播放,监听每个音频的播放完成事件,自动启动下一个
二、完整实现代码
首先要导入必要的框架:
import UIKit import AVFoundation
接下来可以写一个专门的音频管理类(单独的类更便于维护和复用):
class AudioManager: NSObject { // 替换成你自己的服务器音频URL列表 private let audioUrls = [ "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3", "http://your-server.com/audio1.mp3", "http://your-server.com/audio2.mp3" ] private var downloadedLocalUrls: [URL] = [] private var currentPlayPosition = 0 private var activePlayer: AVAudioPlayer? // 启动批量下载+后续播放的完整流程 func startBatchDownloadAndPlay() { let downloadGroup = DispatchGroup() // 遍历所有音频URL,逐个发起下载 for urlString in audioUrls { guard let audioUrl = URL(string: urlString) else { print("无效的音频URL:\(urlString)") continue } downloadGroup.enter() self.downloadSingleAudio(from: audioUrl) { [weak self] isSuccess, localUrl in if isSuccess, let url = localUrl { self?.downloadedLocalUrls.append(url) } downloadGroup.leave() } } // 所有下载任务完成后,启动播放队列 downloadGroup.notify(queue: DispatchQueue.main) { [weak self] in guard let self = self else { return } if self.downloadedLocalUrls.isEmpty { print("没有成功下载任何音频文件") return } print("✅ 所有音频下载完成,开始播放") self.playNextAudio() } } // 单个音频的下载逻辑 private func downloadSingleAudio(from remoteUrl: URL, completion: @escaping (Bool, URL?) -> Void) { let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let localDestinationUrl = documentsDirectory.appendingPathComponent(remoteUrl.lastPathComponent) // 如果文件已经存在,直接返回本地路径 if FileManager.default.fileExists(atPath: localDestinationUrl.path) { print("📁 文件已存在:\(localDestinationUrl.lastPathComponent)") completion(true, localDestinationUrl) return } // 异步下载文件 URLSession.shared.downloadTask(with: remoteUrl) { tempLocation, _, error in guard let tempLoc = tempLocation, error == nil else { print("❌ 下载失败:\(remoteUrl.lastPathComponent),错误:\(error?.localizedDescription ?? "未知错误")") completion(false, nil) return } do { // 将临时文件移动到文档目录 try FileManager.default.moveItem(at: tempLoc, to: localDestinationUrl) print("✅ 下载完成:\(localDestinationUrl.lastPathComponent)") completion(true, localDestinationUrl) } catch { print("❌ 移动文件失败:\(error.localizedDescription)") completion(false, nil) } }.resume() } // 播放下一个音频 private func playNextAudio() { guard currentPlayPosition < downloadedLocalUrls.count else { print("🎵 所有音频播放完毕") return } let targetAudioUrl = downloadedLocalUrls[currentPlayPosition] do { // 初始化播放器 activePlayer = try AVAudioPlayer(contentsOf: targetAudioUrl) activePlayer?.delegate = self activePlayer?.play() print("🎵 正在播放:\(targetAudioUrl.lastPathComponent)") currentPlayPosition += 1 } catch { print("❌ 播放失败:\(error.localizedDescription)") // 播放失败直接跳到下一个,不中断整个流程 currentPlayPosition += 1 playNextAudio() } } } // 实现AVAudioPlayerDelegate,监听播放完成事件 extension AudioManager: AVAudioPlayerDelegate { func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // 当前音频播放结束,自动触发下一个音频的播放 playNextAudio() } }
使用示例
在你的ViewController里调用这个管理类即可启动整个流程:
class ViewController: UIViewController { let audioManager = AudioManager() override func viewDidLoad() { super.viewDidLoad() // 一键启动:先下载所有音频,再依次播放 audioManager.startBatchDownloadAndPlay() } }
三、方案亮点
- 严格的顺序控制:用
DispatchGroup精准等待所有下载完成,不会出现边下载边播放的混乱情况 - 容错能力强:处理了无效URL、文件已存在、下载失败、播放失败等各种异常情况,保证流程能继续
- 内存高效:每次只维护一个活跃的播放器,避免同时创建多个播放器导致内存占用过高
- 易于扩展:可以轻松添加下载进度回调、暂停/继续功能,或者修改播放顺序等
内容的提问来源于stack exchange,提问作者Ankur sharma
相关产品推荐
相关产品推荐

