Swift中如何缓存MP3文件?实现本地复用避免重复下载
MP3音频缓存实现方案(适配你的AVAudioPlayer播放逻辑)
核心思路
- 缓存标识:用音频URL的哈希值作为缓存文件名,保证每个音频对应唯一缓存文件
- 缓存优先级:播放前先检查本地缓存目录,存在则直接加载本地文件;不存在则下载并写入缓存
- 异步优化:替换原同步
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
相关产品推荐
相关产品推荐

