Swift UI自定义视频播放器控件无法正常工作

kmpatx3s  于 12个月前  发布在  Swift
关注(0)|答案(1)|浏览(135)

我有一个自定义的视频播放器,我在一个swift ui视图中使用。我希望能够播放/暂停,静音/取消静音,并快进从swift ui视图的视频。没有控制功能目前正在工作,视频播放器工作正常,但当我尝试设置self.videoDuration = item.duration.seconds时,出现错误“Can 't Publish changes when the view updates”.如何正确实现播放/暂停、静音/取消静音、快进?

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

字符串

n7taea2i

n7taea2i1#

您的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,更改此状态应播放或暂停视频。
(See另一个SwiftUI视频播放器的例子也是perimeter-inc/LegacyVideoPlayer,用于创意/灵感。)
isMuted绑定应该控制视频的静音。请确保此状态在AVPlayer示例中得到正确反映。
当前代码中未实现快进控件。您需要一个方法来更改视频的播放位置。
错误“cannot Publish changes when the view updates“可能意味着在视图更新过程中更新SwiftUI state时出现问题。这可以通过确保在主线程上和视图体外部进行状态更改来解决。
因此,尝试在UserStories中添加一个按钮来快速转发,并在LegacyVideoPlayer中添加一个方法来处理此问题。
修改持续时间更新逻辑,以确保它在主线程上完成,并且不会与SwiftUI的视图更新冲突。
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以防止多次更新。

相关问题