swift AVQueuePlayer播放/暂停方法未更新UI

i5desfxk  于 2023-03-11  发布在  Swift
关注(0)|答案(1)|浏览(132)

我有一个循环视频播放器,它应该根据给定的isPlaying属性来播放或暂停。我最初创建了一个循环视频播放器(类似于this example的实现)--现在我希望能够暂停它。
UI在正确的播放/暂停状态下初始化视频,但当isPlaying更改时,UI不会更新,除非卸载并重新渲染视图。
我使用print来确认isPlaying是正确的值,并且updateUIView调用playpause
我想知道为什么UI不显示更新的视频播放/暂停。
isPlaying被传递到LoopingVideoPlayer,如下所示

...
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()
    }
}
nkoocmlb

nkoocmlb1#

要更新isPlaying的值,你需要使用一个Binding变量。目前,当你将true或false传递给你的LoopingVideoPlayer可表示对象时,你正在创建一个被初始化的值的副本。这意味着当你更新你的变量时,它不会更新LoopingVideoPlayer.。然而,绑定创建了一个单一的真值源。
新代码应如下所示:

struct LoopingVideoPlayer: UIViewRepresentable {
    var fileUrl: URL
    var resizeMode: AVLayerVideoGravity
    @Binding var isPlaying: Bool

    private var loopingPlayerUIView: LoopingPlayerUIView

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

...

然后,在您的父视图中,isPlaying变量应该是State值,如下所示:

...
@State var isPlaying: Bool = true

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

我建议阅读更多关于SwiftUI here中State和Binding之间关系的内容。

相关问题