ios – Swift UI custom video player controls not working


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)
    }
}

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img