SwiftUI macOS应用中AVPlayer关闭Sheet后无法重播CDN视频的问题咨询
SwiftUI macOS应用中AVPlayer关闭Sheet后无法重播CDN视频的问题咨询
看起来你遇到的是AVPlayer播放到结束后状态残留导致的重播问题,我之前也碰到过类似的情况,下面来分析问题并给出具体的解决办法:
问题重述
你在用SwiftUI开发面向macOS 12.0的应用时,通过AVPlayer播放CDN链接视频出现了以下问题:
- 第一次播放正常
- 若视频播放完毕后关闭Sheet,重新打开则无法播放
- 若视频未播放完就关闭Sheet,下次打开可正常播放
- CDN链接长期有效,已确认可访问
你已经尝试过:手动调用player.seek(to: .zero)、在.onAppear中重建Player、验证CDN链接有效性,但问题仍存在。
问题根源分析
当AVPlayerItem完整播放完毕后,它的内部状态会切换到「已播放结束」的状态,此时单纯调用seek(to: .zero)重置播放位置,可能不足以让它回到可播放状态——尤其是CDN来源的视频,Item可能需要重新触发加载逻辑。另外你当前代码里把播放控制放在MacVideoPlayer的.onAppear中,这个时机可能早于Player Item准备好的时间,导致播放指令失效。
具体解决方法
方案1:每次打开Sheet都重建Player并监听Item状态
这是最稳妥的方式,彻底避免状态残留。我们把播放控制逻辑移到父View的.onAppear中,并且监听AVPlayerItem的状态,确保它准备好后再播放:
import SwiftUI import AVKit struct VideoSheet: View { @State var videoURL: URL @Environment(\.dismiss) var dismiss @State private var player: AVPlayer? // 用来保存观察者,防止提前释放 @State private var statusObserver: NSKeyValueObservation? var body: some View { VStack(spacing: 0) { HStack { Spacer() Button(action: { dismiss() }) { Image("cancel") .resizable() .renderingMode(.template) .foregroundStyle(Color("Heading")) .frame(width: 20, height: 20) } .buttonStyle(.plain) } .padding([.top, .trailing], 8) Spacer() if let player = player { MacVideoPlayer(player: player) .cornerRadius(16) } else { ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) } Spacer() } .frame(minWidth: 360, maxWidth: .infinity, minHeight: 640, maxHeight: .infinity) .background(Color("Background")) .onAppear { // 1. 每次打开Sheet都创建全新的Player和Item let playerItem = AVPlayerItem(url: videoURL) let newPlayer = AVPlayer(playerItem: playerItem) // 2. 监听Item的准备状态,就绪后再播放 statusObserver = playerItem.observe(\.status, options: [.new]) { [weak newPlayer] item, _ in guard let player = newPlayer else { return } if item.status == .readyToPlay { player.seek(to: .zero) player.play() } } player = newPlayer } .onDisappear { // 3. 关闭时彻底销毁Player和观察者,清除状态 player?.pause() player?.seek(to: .zero) statusObserver?.invalidate() player = nil } } } struct MacVideoPlayer: NSViewRepresentable { let player: AVPlayer func makeNSView(context: Context) -> AVPlayerView { let view = AVPlayerView() view.controlsStyle = .floating view.player = player return view } func updateNSView(_ nsView: AVPlayerView, context: Context) { nsView.player = player } }
方案2:监听播放结束通知,提前重置状态
如果你不想每次都重建Player,可以监听AVPlayerItemDidPlayToEndTime通知,在视频播放到结束时就主动重置位置,确保下次打开Sheet时状态正常:
struct VideoSheet: View { @State var videoURL: URL @Environment(\.dismiss) var dismiss @State private var player: AVPlayer? var body: some View { // 保留原View结构,仅修改生命周期逻辑 VStack(spacing: 0) { // ... 原View代码省略 ... } .onAppear { player = AVPlayer(url: videoURL) // 监听播放结束通知,提前重置位置 NotificationCenter.default.addObserver( forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main ) { [weak player] _ in player?.seek(to: .zero) } // 打开时重置并触发播放 player?.seek(to: .zero) player?.play() } .onDisappear { player?.pause() player?.seek(to: .zero) // 移除通知观察者,避免内存泄漏 NotificationCenter.default.removeObserver( self, name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem ) player = nil } } }
关键优化点
- 播放控制时机:确保在AVPlayerItem状态为
.readyToPlay时再调用play(),避免指令无效 - 状态彻底清除:Sheet关闭时销毁Player和相关观察者,避免状态残留
- 避免子View控制播放:把播放逻辑移到父View的
.onAppear中,因为子View的生命周期可能和Player准备不同步
这样修改后,无论视频是否播放完毕,下次打开Sheet都能正常触发播放。
内容来源于stack exchange
相关产品推荐
相关产品推荐

