上下文
我正在实现一个应该公开play、pause mute和unmute函数的视频播放器。在我的例子中,如果将这些布尔设置作为绑定传递进来,那将是非常理想的。我有一个基本的UIViewRepresentable在这里工作:
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!) }
}
}使用:
MediaPlayerFeedItem(urls: [url1,url2], shouldPlay: $shouldPlay, shouldMute: .constant(true) )问题是,我不知道如何将UIViewRepresentable与其绑定值连接起来。也就是说,当shouldPlay改为true时,如果MediaPlayerFeedItem只调用play()本身,那就太好了。
===================================================
更新
我忘了提到,在以前的实现中,我有这样的情况:
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
if shouldPlay {
play()
} else {
pause()
}
}这正是MediaPlayerFeedItem行为真正令人困惑的地方。updateViewController及其各自的功能都像预期的那样调用play或pause,但在播放或暂停视频方面,它似乎没有做任何事情。
例如,如果init MediaPlayerFeedItem将shouldPlay设置为真值绑定,那么视频就会播放,而不管稍后会发生什么暂停命令。如果我将MediaPlayerFeedItem设置为shouldPlay设置为false绑定,然后再次将其设置为true,则视频根本不播放。视频加载,它只是停留在第一帧。
我倾向于说问题在于_goPlay函数本身,但是如果我只是在makeUIViewController函数中运行_goPlay,那么视频就会播放和循环,而不管shouldPlay是否设置为true。
根据@aheze的建议,我在play、pause和_goPlay函数中添加了print语句,其行为如下:@aheze --当我插入带有假绑定的MediaFeedItem时,初始化时将得到3条pausing打印。然后10秒后,当我将shouldPlay设置为true时,我会得到一个play打印语句,而没有后续的pause语句。有趣的是,我也从loop play调用的_goPlay函数中获得了NotificationCenter打印语句,因此这意味着视频正在播放。只是没有渲染。注意,我正在初始化每个循环上的所有新AVPlayerItem列表,这是必要的,因为我不能在不同的播放循环中共享它们。
发布于 2021-07-20 13:49:32
弄明白了:
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()
}
}发布于 2021-07-20 02:28:44
请务必查看以下评论:
// @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。所以,你可以这样做:
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
if shouldPlay {
play()
} else {
pause()
}
}https://stackoverflow.com/questions/68448880
复制相似问题