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

如何先从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:08:20