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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 19:09:23