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

Swift中如何缓存MP3文件?实现本地复用避免重复下载

MP3音频缓存实现方案(适配你的AVAudioPlayer播放逻辑)

核心思路

  1. 缓存标识:用音频URL的哈希值作为缓存文件名,保证每个音频对应唯一缓存文件
  2. 缓存优先级:播放前先检查本地缓存目录,存在则直接加载本地文件;不存在则下载并写入缓存
  3. 异步优化:替换原同步Data(contentsOf:)为URLSession异步下载,避免阻塞主线程

具体实现步骤及代码修改

1. 添加缓存路径工具方法

在AudioManager中添加获取缓存目录的方法,统一管理缓存文件的存储位置:

private func getCacheDirectory() -> URL? {
    guard let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
        return nil
    }
    let audioCacheDir = cachesDir.appendingPathComponent("AudioCache")
    // 如果缓存文件夹不存在则创建
    if !FileManager.default.fileExists(atPath: audioCacheDir.path) {
        try? FileManager.default.createDirectory(at: audioCacheDir, withIntermediateDirectories: true)
    }
    return audioCacheDir
}

private func getCachedFileURL(for trackURL: URL) -> URL? {
    guard let cacheDir = getCacheDirectory() else { return nil }
    // 用URL的哈希值作为文件名,避免特殊字符问题
    let fileName = "\(trackURL.hashValue).mp3"
    return cacheDir.appendingPathComponent(fileName)
}

2. 重构startPlayer方法,加入缓存逻辑

替换原同步加载逻辑,改为先检查缓存,再异步下载并缓存:

func startPlayer(track: String) {
    guard let trackURL = URL(string: track) else { return }
    
    // 先检查本地缓存
    if let cachedFileURL = getCachedFileURL(for: trackURL), FileManager.default.fileExists(atPath: cachedFileURL.path) {
        loadPlayer(from: cachedFileURL)
        return
    }
    
    // 没有缓存则异步下载
    URLSession.shared.dataTask(with: trackURL) { [weak self] data, response, error in
        guard let self = self, let data = data, error == nil else {
            print("音频下载失败:\(error?.localizedDescription ?? "未知错误")")
            return
        }
        
        // 将下载的数据写入缓存
        if let cachedFileURL = self.getCachedFileURL(for: trackURL) {
            do {
                try data.write(to: cachedFileURL)
                // 主线程加载并播放
                DispatchQueue.main.async {
                    self.loadPlayer(from: cachedFileURL)
                }
            } catch {
                print("缓存写入失败:\(error)")
                // 缓存写入失败,直接用内存数据播放
                DispatchQueue.main.async {
                    self.loadPlayer(from: data)
                }
            }
        } else {
            // 无法获取缓存路径,直接用内存数据播放
            DispatchQueue.main.async {
                self.loadPlayer(from: data)
            }
        }
    }.resume()
}

// 封装AVAudioPlayer初始化逻辑,支持本地URL和Data
private func loadPlayer(from localURL: URL) {
    do {
        try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
        try AVAudioSession.sharedInstance().setActive(true)
        
        self.player = try AVAudioPlayer(contentsOf: localURL)
        guard let player = player else { return }
        player.prepareToPlay()
        player.play()
        isPlaying = true
    } catch {
        print("加载本地音频失败:\(error)")
    }
}

private func loadPlayer(from data: Data) {
    do {
        try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
        try AVAudioSession.sharedInstance().setActive(true)
        
        self.player = try AVAudioPlayer(data: data)
        guard let player = player else { return }
        player.prepareToPlay()
        player.play()
        isPlaying = true
    } catch {
        print("加载音频数据失败:\(error)")
    }
}

3. 保留原有控制逻辑

原有的playPause和stop方法无需修改,直接保留即可。

关键细节说明

  • 缓存位置:使用应用的Caches目录,系统在磁盘空间不足时会自动清理该目录下的文件,符合iOS缓存规范
  • 异步处理:所有网络操作和文件写入都放在后台线程,避免阻塞主线程导致UI卡顿
  • 容错处理:缓存写入失败时,仍能直接使用内存中的音频数据播放,保证功能可用性
  • 文件名安全:用URL的哈希值作为文件名,避免URL中的特殊字符导致文件路径错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:55:21