SwiftUI获取服务器音频时长致应用冻结,求解决方法
问题:获取远程音频时长导致应用UI冻结
我一直在寻找显示服务器存储音频文件时长的方法,在Stack Overflow上找到一段代码,原本以为可行,但调用时会导致导航到对应视图时应用完全冻结。
原参考代码
let item = AVPlayerItem(url: url) let duration = Double(item.asset.duration.value) / Double(item.asset.duration.timescale)
我的实现代码
获取时长的函数
func getDuration(url: URL) -> Double { let item = AVPlayerItem(url: url) let duration = Double(item.asset.duration.value) / Double(item.asset.duration.timescale) return duration }
视图中的调用
Text(String(audioManager.getDuration(url: URL(string: "http://gameleprilucky.ru/soungs/" + (data.linkSoung ?? "abob"))!))) .font(.custom("Manrope-Medium", size: 12)) .foregroundColor(.white)
问题原因
直接在主线程同步获取远程音频的时长,会触发网络请求和元数据加载的耗时操作,阻塞UI线程导致应用冻结。AVPlayerItem的asset默认不会自动加载远程资源的元数据,同步访问duration会强制触发同步加载,卡住主线程。
解决方法
必须通过异步加载音频元数据的方式处理,避免阻塞主线程。以下是几种可行方案:
方案1:用async/await封装异步函数(推荐,Swift 5.5+)
修改获取时长的函数为异步函数,利用AVAsset的异步加载API:
func getDuration(url: URL) async throws -> Double { let asset = AVAsset(url: url) // 异步加载duration元数据 try await asset.load(.duration) let duration = asset.duration return Double(duration.value) / Double(duration.timescale) }
在视图中通过Task异步调用,用状态变量更新UI:
struct AudioView: View { let data: YourDataType // 替换为你的数据类型 @State private var audioDuration: Double? = nil let audioManager = YourAudioManager() // 替换为你的音频管理实例 var body: some View { Text(audioDuration != nil ? String(format: "%.1f", audioDuration!) : "--") .font(.custom("Manrope-Medium", size: 12)) .foregroundColor(.white) .task { guard let link = data.linkSoung, let url = URL(string: "http://gameleprilucky.ru/soungs/" + link) else { return } do { audioDuration = try await audioManager.getDuration(url: url) } catch { print("获取时长失败:\(error)") } } } }
方案2:闭包回调(兼容旧版本Swift)
如果不支持async/await,用闭包异步返回结果:
func getDuration(url: URL, completion: @escaping (Double?) -> Void) { let asset = AVAsset(url: url) asset.loadValuesAsynchronously(forKeys: ["duration"]) { var error: NSError? let status = asset.statusOfValue(forKey: "duration", error: &error) DispatchQueue.main.async { switch status { case .loaded: let duration = asset.duration completion(Double(duration.value) / Double(duration.timescale)) case .failed, .cancelled: print("加载时长失败:\(error?.localizedDescription ?? "未知错误")") completion(nil) default: completion(nil) } } } }
在视图中调用并更新状态:
struct AudioView: View { let data: YourDataType @State private var audioDuration: Double? = nil let audioManager = YourAudioManager() var body: some View { Text(audioDuration != nil ? String(format: "%.1f", audioDuration!) : "--") .font(.custom("Manrope-Medium", size: 12)) .foregroundColor(.white) .onAppear { guard let link = data.linkSoung, let url = URL(string: "http://gameleprilucky.ru/soungs/" + link) else { return } audioManager.getDuration(url: url) { duration in self.audioDuration = duration } } } }
关键注意事项
- 绝对不要在主线程执行网络请求或元数据加载这类耗时操作,必须异步处理。
- 确保元数据加载完成后再访问
duration,避免触发同步阻塞。 - 用视图状态变量存储时长,异步获取结果后再更新UI,保证界面流畅。
内容的提问来源于stack exchange,提问作者user19727360
相关产品推荐
相关产品推荐

