SwiftUI:如何在使用 ImagePickerController 从设备加载视频后正确编码 AVPlayer?

SwiftUI: How to Properly Code AVPlayer After Loading A Video From the Device with an ImagePickerController?

我使用 SwiftUI 创建了一个视频播放器,它使用 imagePickerController 加载视频,然后假设从设备检索到视频后播放视频。我发现视频播放器在检索视频后没有刷新。我不确定如何提供刷新它所必需的适当的@State|@Binding。

我学会了如何使用可用的在线资源编写视频播放器代码。我找到了一种从我的设备加载视频并将其加载到我的视频播放器的方法。但是,当我按下播放按钮时,加载视频后,只播放了声音。我尝试制作视频播放器@State|@Binding,但找不到解决方案,因为它似乎不是直观地这样做的。

任何人都可以建议如何使用 SwiftUI 更新我的视频播放器代码吗?

P.S. 1)您必须使用实际设备加载视频;和 2) 滑块还没有工作。接下来我会继续努力。

披露:

我从在线资源中改编了这段代码。 大部分工作的原始源代码可以在这些链接中找到:

https://www.raywenderlich.com/5135-how-to-play-record-and-merge-videos-in-ios-and-swift

https://medium.com/@chris.mash/avplayer-swiftui-part-2-player-controls-c28b721e7e27

import SwiftUI
import AVKit
import PhotosUI
import MobileCoreServices

struct ContentView: View {

    @State var showImagePicker: Bool = false
    @State var url: URL?

    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    withAnimation {
                        self.showImagePicker.toggle()
                    }
                }) {
                    Text("Show image picker")
                }

                // The video player will needs to be a @State??? as it is not updated with UIView changes but works when no view changes occur.
                PlayerContainerView(player: AVPlayer(url: url ?? URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
            }

            if (showImagePicker) {
                ImagePicker(isShown: $showImagePicker, url: $url)
            }
        }
    }
}

struct PlayerView: UIViewRepresentable {
    let player: AVPlayer
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
    }
    func makeUIView(context: Context) -> UIView {
        return PlayerUIView(player: player)
    }
}

class PlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()
    init(player: AVPlayer) {
        super.init(frame: .zero)

        playerLayer.player = player
        layer.addSublayer(playerLayer)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
}

struct PlayerContainerView : View {

    @State var seekPos = 0.0

    private let player: AVPlayer
    init(player: AVPlayer) {
        self.player = player
    }
    var body: some View {
        VStack {
            PlayerView(player: player)
            PlayerControlsView(player: player)
        }
    }
}

struct PlayerControlsView : View {
    @State var playerPaused = true
    @State var seekPos = 0.0
    let player: AVPlayer
    var body: some View {
        HStack {
            Button(action: {
                self.playerPaused.toggle()
                if self.playerPaused {
                    self.player.pause()
                }
                else {
                    self.player.play()
                }
            }) {
                Image(systemName: playerPaused ? "play" : "pause")
                    .padding(.leading, 20)
                    .padding(.trailing, 20)
            }
            Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
                guard let item = self.player.currentItem else {
                    return
                }

                let targetTime = self.seekPos * item.duration.seconds
                self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
            })
                .padding(.trailing, 20)
        }
    }
}

struct ImagePicker: UIViewControllerRepresentable {

    @Binding var isShown: Bool
    @Binding var url: URL?

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

        @Binding var isShown: Bool
        @Binding var url: URL?

        init(isShown: Binding<Bool>, url: Binding<URL?>) {
            $isShown = isShown
            $url = url
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

            let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
            guard let mediaType = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as? String,
                mediaType == (kUTTypeMovie as String),
                let uiURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL
                else { return }

            url = uiURL
            isShown = false
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            isShown = false
        }

    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(isShown: $isShown, url: $url)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.mediaTypes = [kUTTypeMovie as String]
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController,
                                context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}

fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] {
    return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
}

fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String {
    return input.rawValue
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView(showImagePicker: true)
    }
}
#endif

视频播放器没有按预期播放视频,它只播放了提示我正在播放视频的声音;但是,我看不到它正在播放。它仍然是一个黑盒子。

更新: 以下是完全编辑后的代码,按预期工作(滑块除外),在下面的评论中得到了回答:

import SwiftUI
import AVKit
import PhotosUI
import MobileCoreServices

struct ContentView: View {

    @State var showImagePicker: Bool = false
    @State var url: URL?

    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    withAnimation {
                        self.showImagePicker.toggle()
                    }
                }) {
                    Text("Show image picker")
                }

                PlayerContainerView(player: AVPlayer(url: url ?? URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
            }

            if (showImagePicker) {
                ImagePicker(isShown: $showImagePicker, url: $url)
            }
        }
    }
}

struct PlayerView: UIViewRepresentable {
    let player: AVPlayer
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
        (uiView as? PlayerUIView)?.updatePlayer(player: player)
    }

    func makeUIView(context: Context) -> UIView {
        return PlayerUIView(player: player)
    }
}

class PlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()
    init(player: AVPlayer) {
        super.init(frame: .zero)

        playerLayer.player = player
        layer.addSublayer(playerLayer)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }

    func updatePlayer(player: AVPlayer) {
        self.playerLayer.player = player
    }
}

struct PlayerContainerView : View {

    @State var seekPos = 0.0

    private let player: AVPlayer
    init(player: AVPlayer) {
        self.player = player
    }
    var body: some View {
        VStack {
            PlayerView(player: player)
            PlayerControlsView(player: player)
        }
    }
}

struct PlayerControlsView : View {
    @State var playerPaused = true
    @State var seekPos = 0.0
    let player: AVPlayer
    var body: some View {
        HStack {
            Button(action: {
                self.playerPaused.toggle()
                if self.playerPaused {
                    self.player.pause()
                }
                else {
                    self.player.play()
                }
            }) {
                Image(systemName: playerPaused ? "play" : "pause")
                    .padding(.leading, 20)
                    .padding(.trailing, 20)
            }
            Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
                guard let item = self.player.currentItem else {
                    return
                }

                let targetTime = self.seekPos * item.duration.seconds
                self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
            })
                .padding(.trailing, 20)
        }
    }
}

struct ImagePicker: UIViewControllerRepresentable {

    @Binding var isShown: Bool
    @Binding var url: URL?

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

        @Binding var isShown: Bool
        @Binding var url: URL?

        init(isShown: Binding<Bool>, url: Binding<URL?>) {
            _isShown = isShown
            _url = url
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

            let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
            guard let mediaType = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as? String,
                mediaType == (kUTTypeMovie as String),
                let uiURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL
                else { return }

            url = uiURL
            isShown = false
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            isShown = false
        }

    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(isShown: $isShown, url: $url)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.mediaTypes = [kUTTypeMovie as String]
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController,
                                context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}

fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] {
    return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
}

fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String {
    return input.rawValue
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView(showImagePicker: true)
    }
}
#endif

您将 updateUIView 留空了。你应该实现它:

func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
    (uiView as? PlayerUIView)?.updatePlayer(player: player)
}

并且还将以下方法添加到您的 PlayerUIView:

func updatePlayer(player: AVPlayer) {
    self.playerLayer.player = player
}