I have a custom video player that I use in a swift ui view. I want to be able to play/pause, mute/unmute, and fast forward the video from the swift ui view. None of the controls functionality is currently working, the video player works just fine but I get the error “Can’t Publish changes when the view updates” when I try to set self.videoDuration = item.duration.seconds. How can I correctly implement play/pause, mute/unmute, and fast forward?
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)
}
}




