我有一个自定义视频播放器,可以在 swift ui 视图中使用。我希望能够从 swift ui 视图播放/暂停、静音/取消静音以及快进视频。目前所有控件功能均无法正常工作,视频播放器工作正常,但当我尝试设置 self.videoDuration = item.duration.seconds 时,出现错误“视图更新时无法发布更改”。如何正确实现播放/暂停、静音/取消静音、快进?
import Foundation
import SwiftUI
import AVKit
struct UserStories: View {
@State private var isPlaying = true
@State private var videoDuration: Double = 0
@State private var isMuted = false
var body: some View {
VStack {
LegacyVideoPlayer(url: URL(string: "")!, isPlaying: $isPlaying, videoDuration: $videoDuration, isMuted: $isMuted)
HStack {
Button("Play") {
isPlaying = true
}
Button("Pause") {
isPlaying = false
}
Button("Toggle Mute") {
isMuted.toggle()
}
}
Text("Video Duration: \(videoDuration)")
}
}
}
public struct LegacyVideoPlayer: UIViewControllerRepresentable {
let url: URL
@Binding var isPlaying: Bool
@Binding var videoDuration: Double
@Binding var isMuted: Bool
public func makeCoordinator() -> CustomPlayerCoordinator {
CustomPlayerCoordinator(customPlayer: self)
}
public func makeUIViewController(context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) -> LegacyAVPlayerViewController {
let controller = LegacyAVPlayerViewController(videoDuration: $videoDuration, isMuted: $isMuted)
controller.delegate = context.coordinator
makeAVPlayer(in: controller, context: context)
playIfNeeded(controller.player)
return controller
}
public func updateUIViewController(_ uiViewController: LegacyAVPlayerViewController, context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) {
makeAVPlayer(in: uiViewController, context: context)
playIfNeeded(uiViewController.player)
uiViewController.isMuted = isMuted
}
private func makeAVPlayer(in controller: LegacyAVPlayerViewController, context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) {
let item = AVPlayerItem(url: url)
let player = AVQueuePlayer(playerItem: item)
let loopingPlayer = AVPlayerLooper(player: player, templateItem: item)
controller.videoGravity = AVLayerVideoGravity.resizeAspectFill
context.coordinator.loopingPlayer = loopingPlayer
controller.player = player
controller.showsPlaybackControls = false
}
private func playIfNeeded(_ player: AVPlayer?) {
if isPlaying { player?.play() }
else { player?.pause() }
}
}
public class LegacyAVPlayerViewController: AVPlayerViewController {
@Binding var videoDuration: Double
@Binding var isMuted: Bool
private var rateObserver: NSKeyValueObservation?
private var durationObserver: NSKeyValueObservation?
public override var player: AVPlayer? {
willSet {
rateObserver?.invalidate()
durationObserver?.invalidate()
}
didSet {
if rateObserver == nil {
rateObserver = player?.observe(\AVPlayer.rate, options: [.new], changeHandler: rateHandler(_:change:))
}
}
}
deinit {
rateObserver?.invalidate()
durationObserver?.invalidate()
}
private func rateHandler(_ player: AVPlayer, change: NSKeyValueObservedChange<Float>) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let item = player.currentItem,
item.currentTime().seconds > 0.5,
player.status == .readyToPlay
else { return }
self.videoDuration = item.duration.seconds
}
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player?.pause()
}
init(videoDuration: Binding<Double>, isMuted: Binding<Bool>) {
_videoDuration = videoDuration
_isMuted = isMuted
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public class CustomPlayerCoordinator: NSObject, AVPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate {
let customPlayer: LegacyVideoPlayer
public init(customPlayer: LegacyVideoPlayer) {
self.customPlayer = customPlayer
super.init()
}
public func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
completionHandler(true)
}
}
您的 SwiftUI 视频播放器设置看起来相当复杂:
+---------------------------------------------+
| UserStories (SwiftUI View) |
| +-----------------------------------------+ |
| | LegacyVideoPlayer (UIViewControllerRep) | |
| | +-------------------------------------+ | |
| | | LegacyAVPlayerViewController | | |
| | | (AVPlayerViewController) | | |
| | | + Player (AVPlayer) | | |
| | | + Video Duration (Binding<Double>) | | |
| | | + Is Muted (Binding<Bool>) | | |
| | +-------------------------------------+ | |
| +-----------------------------------------+ |
| + HStack for Play/Pause/Mute buttons |
+---------------------------------------------+
播放/暂停控件似乎在
UserStories
视图中得到了正确处理。 isPlaying
状态绑定到 LegacyVideoPlayer
,更改此状态应播放或暂停视频。perimeter-inc/LegacyVideoPlayer
,了解 SwiftUI 视频播放器的另一个示例,以获取想法/灵感。)
isMuted
绑定应控制视频的静音。确保此状态正确反映在 AVPlayer
实例中。
您当前的代码中未实现快进控件。您将需要一种方法来更改视频的播放位置。
错误“
cannot Publish changes when the view updates
”可能意味着在视图更新期间更新SwiftUI状态出现问题。这可以通过确保在主线程上和视图主体之外进行状态更改来解决。
因此,尝试在
UserStories
中添加一个用于快进的按钮,并在 LegacyVideoPlayer
中添加一个方法来处理此问题。在
UserStories
:
Button("Fast Forward 10s") {
legacyVideoPlayerRef.fastForward(seconds: 10)
}
在
LegacyVideoPlayer
:
// Add a reference to the controller
private var playerController: LegacyAVPlayerViewController?
public func fastForward(seconds: Double) {
guard let currentPlayer = playerController?.player else { return }
let currentTime = currentPlayer.currentTime()
let newTime = CMTimeAdd(currentTime, CMTimeMakeWithSeconds(seconds, preferredTimescale: currentTime.timescale))
currentPlayer.seek(to: newTime)
}
// In makeUIViewController:
playerController = controller
在
LegacyAVPlayerViewController
中,修改 rateHandler
以确保持续时间正确更新:
private func rateHandler(_ player: AVPlayer, change: NSKeyValueObservedChange<Float>) {
DispatchQueue.main.async { [weak self] in
guard let self = self, let item = player.currentItem, item.currentTime().seconds > 0.5, player.status == .readyToPlay else { return }
if !self.isDurationSet {
self.videoDuration = item.duration.seconds
self.isDurationSet = true
}
}
}
添加标记
isDurationSet
以防止多次更新。