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

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
        }
    }
}

关键优化点

  1. 播放控制时机:确保在AVPlayerItem状态为.readyToPlay时再调用play(),避免指令无效
  2. 状态彻底清除:Sheet关闭时销毁Player和相关观察者,避免状态残留
  3. 避免子View控制播放:把播放逻辑移到父View的.onAppear中,因为子View的生命周期可能和Player准备不同步

这样修改后,无论视频是否播放完毕,下次打开Sheet都能正常触发播放。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 11:04:34