首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SwiftUI + UIViewRepresentable:您将如何让UIViewRepresentable响应绑定

SwiftUI + UIViewRepresentable:您将如何让UIViewRepresentable响应绑定
EN

Stack Overflow用户
提问于 2021-07-20 02:22:24
回答 2查看 1K关注 0票数 2

上下文

我正在实现一个应该公开playpause muteunmute函数的视频播放器。在我的例子中,如果将这些布尔设置作为绑定传递进来,那将是非常理想的。我有一个基本的UIViewRepresentable在这里工作:

代码语言:javascript
复制
import SwiftUI
import AVKit
import UIKit


//MARK:- video player with play/pause API

struct MediaPlayerFeedItem : UIViewControllerRepresentable {
    
    var urls: [String]
    @Binding var shouldPlay: Bool
    @Binding var shouldMute: Bool

    var vc =  AVPlayerViewController()
    @State var pauseAll: Bool = false
    

    //MARK:- API
        
    // @use: play from beginning
    func play(){
        self._goPlay()
    }
        
    // @use: pause video
    func pause(){
        vc.player?.pause()
    }
    
    // @use respond to mute event
    func mute(){
    }
    
    // @use: respond to umute event
    func unmute(){
    }
    
        
    //MARK:- view
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        if shouldPlay {
            _goPlay()
        } else {
            let items = _makeItems()
            if items.count > 0 {
                let player = AVQueuePlayer(items: items)
                vc.player =  player
                vc.showsPlaybackControls = false
                vc.videoGravity = .resizeAspectFill
                vc.player?.seek(to: .zero)
                vc.player?.pause()
            }
        }
        return vc
    }
    
    // @use: this fn will fire when `shouldPlay` is updated. at which point you can
    //       `play` or `pause` the video as desired
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
    }
    
    //MARK:- playback

    private func _goPlay(){

        let items = _makeItems()
        guard items.count > 0 else { return }
        
        let player = AVQueuePlayer(items: items)
        vc.player =  player
        vc.showsPlaybackControls = false
        vc.videoGravity = .resizeAspectFill
        vc.player?.seek(to: .zero)
        vc.player?.play()
       
        NotificationCenter.default
            .addObserver(
                forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                object: items[items.count-1],
                queue: .main
            ){_ in
                self._goPlay()
            }

    }
    
    // @use: make a new set of avplayeritems before each
    // invocation of _goPlay loop
    private func _makeItems() -> [AVPlayerItem] {
        return urls
            .map{ URL(string: $0) }
            .filter{ $0 != nil }
            .map{ AVPlayerItem(url: $0!) }
    }
    
    

}

使用:

代码语言:javascript
复制
  MediaPlayerFeedItem(urls: [url1,url2], shouldPlay: $shouldPlay, shouldMute: .constant(true) )

问题是,我不知道如何将UIViewRepresentable与其绑定值连接起来。也就是说,当shouldPlay改为true时,如果MediaPlayerFeedItem只调用play()本身,那就太好了。

===================================================

更新

我忘了提到,在以前的实现中,我有这样的情况:

代码语言:javascript
复制
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
    if shouldPlay {
        play()
    } else {
        pause()
    }
}

这正是MediaPlayerFeedItem行为真正令人困惑的地方。updateViewController及其各自的功能都像预期的那样调用playpause,但在播放或暂停视频方面,它似乎没有做任何事情。

例如,如果init MediaPlayerFeedItemshouldPlay设置为真值绑定,那么视频就会播放,而不管稍后会发生什么暂停命令。如果我将MediaPlayerFeedItem设置为shouldPlay设置为false绑定,然后再次将其设置为true,则视频根本不播放。视频加载,它只是停留在第一帧。

我倾向于说问题在于_goPlay函数本身,但是如果我只是在makeUIViewController函数中运行_goPlay,那么视频就会播放和循环,而不管shouldPlay是否设置为true。

根据@aheze的建议,我在playpause_goPlay函数中添加了print语句,其行为如下:@aheze --当我插入带有假绑定的MediaFeedItem时,初始化时将得到3条pausing打印。然后10秒后,当我将shouldPlay设置为true时,我会得到一个play打印语句,而没有后续的pause语句。有趣的是,我也从loop play调用的_goPlay函数中获得了NotificationCenter打印语句,因此这意味着视频正在播放。只是没有渲染。注意,我正在初始化每个循环上的所有新AVPlayerItem列表,这是必要的,因为我不能在不同的播放循环中共享它们。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-07-20 13:49:32

弄明白了:

代码语言:javascript
复制
struct VideoPlayerView: UIViewRepresentable {

    var urls: [URL]
    var playOnLoad: Bool
    @EnvironmentObject var appState: AppState

    
    func makeUIView(context: Context) -> UIView {
        return PlayerUIViewQueue(urls: urls, playOnLoad: playOnLoad, frame: .zero)
    }
    
    // @use: on `appState` context change, `play` `pause` or `resume` the video
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<VideoPlayerView>) {

        if let uiv = uiView as? PlayerUIViewQueue {
            
            switch appState.getTokenPlayCommand() {
            case .pause:
                uiv.pause()
            case .play:
                uiv.playFromBeginning()
            case .resume:
                uiv.resume()
            }
        }
    }

}


class PlayerUIViewQueue: UIView {
    
    private var URLs: [URL]

    // player references
    private let playerLayer = AVPlayerLayer()
    private var current_mutli : AVQueuePlayer? = nil
    private var current_single: AVPlayer? = nil
    
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

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


    init(urls: [URL], playOnLoad:Bool, frame: CGRect){
        
        self.URLs = urls

        super.init(frame: frame)
        
        if self.URLs.count == 1 {
            print("if case")
            initSinglePlayer()
        } else {
            print("else case")
            loopAll()
        }
    }
    

    //MARK:- API
    
    func resume(){
        print("RESUMNING")
        current_mutli?.play()
        current_single?.play()
    }
    
    func pause(){
        print("PAUSING")
        current_mutli?.pause()
        current_single?.pause()
    }
    
    func playFromBeginning(){
        print("playFromBeginning")
        current_mutli?.seek(to: .zero)
        current_mutli?.play()
        current_single?.seek(to: .zero)
        current_single?.play()
    }
    
    
    //MARK:- player utils
    
    private func initSinglePlayer(){
        
        if URLs.count == 0 { return }

        let player = AVPlayer(url: URLs[0])
        player.actionAtItemEnd = .none
        self.current_single = player

        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill

        layer.addSublayer(playerLayer)

        player.play()
        NotificationCenter.default
            .addObserver(
                self,
                selector: #selector(loopPlayerSingle(notification:)),
                name: .AVPlayerItemDidPlayToEndTime,
                object: player.currentItem
            )
        
    }
    
    
    @objc func loopPlayerSingle(notification: Notification) {
        if let playerItem = notification.object as? AVPlayerItem {
            playerItem.seek(to: .zero, completionHandler: nil)
        }
    }

    
    // @use: on each `loopAll()` invocation, reinit
    // the player with all video `items`
    private func loopAll(){

        let items  = URLs.map{ AVPlayerItem.init(url: $0) }
        let player = AVQueuePlayer(items: items)
        self.playerLayer.player = player
        self.current_mutli = player

        player.seek(to: .zero)
        player.play()
        
        playerLayer.videoGravity = .resizeAspectFill
        layer.addSublayer(playerLayer)
        
        NotificationCenter.default
            .addObserver(
                self,
                selector: #selector(loopPlayerMulti(notification:)),
                name: .AVPlayerItemDidPlayToEndTime,
                object: player.items().last
            )
        
    }
    

    
    @objc private func loopPlayerMulti(notification: Notification) {
        loopAll()
    }

}
票数 0
EN

Stack Overflow用户

发布于 2021-07-20 02:28:44

请务必查看以下评论:

代码语言:javascript
复制
// @use: this fn will fire when `shouldPlay` is updated. at which point you can
//       `play` or `pause` the video as desired
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {

如注释所述,每当shouldPlay被更新时,都会调用updateUIViewController。所以,你可以这样做:

代码语言:javascript
复制
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
    if shouldPlay {
        play()
    } else {
        pause()
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68448880

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档