SwiftUI中如何实现类似AsyncImage的AVPlayer异步视频加载
解决方案
核心问题是AVPlayer没有像AsyncImage那样内置异步加载状态管理,视图提前渲染时拿不到有效URL,我们可以参照AsyncImage的状态切换逻辑,手动管理视频加载的全流程状态,等URL请求完成后再初始化播放器。
具体修改步骤
- 移除视图初始化时创建的空AVPlayer实例,改为可选类型的AVPlayer状态,初始值为nil
- 新增加载状态标记,在加载过程中展示占位内容(进度条、占位图都可以)
- 等异步方法拿到有效视频URL之后,再用该URL初始化AVPlayer,切换到视频播放视图
修改后可直接复用的代码
import AVKit import SwiftUI struct RecentCard: View { // 初始不创建空播放器,等拿到URL后再初始化 @State private var player: AVPlayer? @State private var playToggle: Bool = true @State var video: VideoModel // 加载状态标记,对应AsyncImage的不同渲染阶段 @State private var isLoading = true @StateObject var vManager = VideoManager() var body: some View { ZStack(alignment: .leading) { // 按加载状态切换渲染内容 if isLoading { // 加载中占位,和AsyncImage的placeholder逻辑一致 ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let player = player { // 确认播放器实例有效后再渲染播放层 VideoPlayer(player: player) .onAppear { if playToggle { player.play() } } .onDisappear { player.pause() } } else { // 加载失败占位 Image(systemName: "video.slash.fill") .foregroundColor(.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity) } textOverlay } .frame(maxWidth: .infinity, maxHeight: 220, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20)) .cornerRadius(20) .task { isLoading = true await vManager.loadVideos() video = vManager.video // 校验URL有效性后再初始化播放器 guard let videoURL = video.videoURL else { isLoading = false return } player = AVPlayer(url: videoURL) // 这里可以加自定义播放器配置,比如默认静音、循环播放 player?.isMuted = true isLoading = false } } // 保留原有文字覆盖层逻辑即可 @ViewBuilder var textOverlay: some View { // 原有业务代码 EmptyView() } }
注意事项
- 如果你的
loadVideos()方法会抛出错误,记得用do-catch包裹,处理加载失败的场景,避免加载状态一直卡在loading - 如果是在列表中使用该卡片组件,建议加载URL时传入当前卡片对应的视频ID,拿到URL后先校验ID和当前卡片匹配再给播放器赋值,避免列表复用导致视频串流
- 不需要手动给AVPlayer提前传空URL占位,无效的初始化不仅会报nil参数错误,还会造成不必要的资源开销
内容的提问来源于stack exchange,提问作者Apekshik Panigrahi
相关产品推荐
相关产品推荐

