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

AVQueuePlayer的play/pause方法无法更新SwiftUI界面问题求助

循环视频播放器无法响应isPlaying状态更新的问题

我实现了一个循环视频播放器,期望通过传入的isPlaying属性控制播放/暂停,初始状态正常,但isPlaying变更时,除非卸载重渲染视图,否则视频状态不会更新。已经确认isPlaying值正确,且updateUIView确实调用了play/pause方法,想知道原因。

传入isPlaying的代码:

...
var body: some View {
    LoopingVideoPlayer(
      fileUrl: localUrl,
      resizeMode: .resizeAspectFill, 
      isPlaying: isPlaying)
}
...

播放器完整代码:

import SwiftUI
import AVKit
import AVFoundation

struct LoopingVideoPlayer: UIViewRepresentable {
    var fileUrl: URL
    var resizeMode: AVLayerVideoGravity
    var isPlaying: Bool = true

    private var loopingPlayerUIView: LoopingPlayerUIView

    init(fileUrl: URL, resizeMode: AVLayerVideoGravity, isPlaying: Bool = true) {
        self.fileUrl = fileUrl
        self.resizeMode = resizeMode
        self.isPlaying = isPlaying
        self.loopingPlayerUIView = LoopingPlayerUIView(frame: .zero)
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LoopingVideoPlayer>) {
        print("updateUIView called while isPlaying == \(isPlaying)")
        if isPlaying {
            loopingPlayerUIView.play()
        } else {
            loopingPlayerUIView.pause()
        }
    }

    func makeUIView(context: Context) -> UIView {
        print("makeUIView called")
        loopingPlayerUIView.initPlayer(fileUrl: fileUrl, resizeMode: resizeMode)
        return loopingPlayerUIView
    }
}

class LoopingPlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()
    private let queuePlayer = AVQueuePlayer()
    private var playerLooper: AVPlayerLooper?

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        playerLayer.player = queuePlayer
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }

    func initPlayer(fileUrl: URL, resizeMode: AVLayerVideoGravity) {
        print("initPlayer method invoked")
        let avAsset = AVAsset(url: fileUrl)
        let avPlayerItem = AVPlayerItem(asset: avAsset)

        playerLayer.videoGravity = resizeMode
        layer.addSublayer(playerLayer)

        playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: avPlayerItem)
    }

    func play() {
        print("play method invoked")
        print("playerLayer reference matches queuePlayer: \(queuePlayer === playerLayer.player)")
        queuePlayer.play()
    }

    func pause() {
        print("pause method invoked")
        print("playerLayer reference matches queuePlayer: \(queuePlayer === playerLayer.player)")
        queuePlayer.pause()
    }
}

问题原因与修复方案

核心问题

你在LoopingVideoPlayer中把loopingPlayerUIView声明为私有存储属性,并在init里初始化。但UIViewRepresentable的机制是:当绑定属性更新时,LoopingVideoPlayer结构体(值类型)会被重新初始化,而makeUIView只会调用一次(除非视图被销毁重建)。这导致updateUIView中调用的loopingPlayerUIView是结构体新实例里的对象,和makeUIView返回的、显示在屏幕上的实例完全不是同一个——你操作的是一个没有添加到视图层级的空对象,自然看不到状态变化。

修复步骤

步骤1:移除私有存储属性,直接操作视图实例

updateUIView的参数uiView就是makeUIView返回的实例,直接把它强转为LoopingPlayerUIView即可:

修改LoopingVideoPlayer代码:

struct LoopingVideoPlayer: UIViewRepresentable {
    let fileUrl: URL
    let resizeMode: AVLayerVideoGravity
    let isPlaying: Bool

    // 移除原来的private var loopingPlayerUIView

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LoopingVideoPlayer>) {
        guard let playerView = uiView as? LoopingPlayerUIView else { return }
        print("updateUIView called while isPlaying == \(isPlaying)")
        if isPlaying {
            playerView.play()
        } else {
            playerView.pause()
        }
    }

    func makeUIView(context: Context) -> UIView {
        print("makeUIView called")
        let playerView = LoopingPlayerUIView(frame: .zero)
        playerView.initPlayer(fileUrl: fileUrl, resizeMode: resizeMode)
        // 初始化时同步isPlaying状态
        if isPlaying {
            playerView.play()
        } else {
            playerView.pause()
        }
        return playerView
    }
}

步骤2:验证修复效果

修复后,play/pause方法中打印的queuePlayer === playerLayer.player应该始终为true,且isPlaying变更时,屏幕上的视频会立即响应播放/暂停状态。

额外建议

  • 将fileUrl、resizeMode、isPlaying声明为let,符合Swift值类型的最佳实践。
  • 如果需要更复杂的状态管理,可以使用Coordinator模式,但当前场景下直接强转uiView足够简洁。

内容的提问来源于stack exchange,提问作者Jake Loew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 14:17:48