Аудио продолжает воспроизводиться после удаления AVPlayer из VStack (SwiftUI)

#ios #swift #macos #swiftui

#iOS #swift #macos #swiftui

Вопрос:

У меня есть следующий VStack, который содержит AVPlayer (в PlayerView):

 struct ContentView: View {
    @State var url: URL?
    private let openFile = NotificationCenter.default.publisher(for: .openFile)

    var body: some View {
        VStack {
            if isVideo(self.url!) {
                PlayerView(url: self.url!)
            } else {
                Image(nsImage: NSImage(contentsOf: self.url!)!).resizable().aspectRatio(contentMode: .fit)
            }

        }.onReceive(openFile) { notification in
            self.url = notification.object as? URL
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  

И это PlayerView:

 struct PlayerView: NSViewRepresentable {
    private var url: URL
    init(url: URL) {
        self.url = url
    }

    func updateNSView(_ nsView: PlayerNSView, context _: NSViewRepresentableContext<PlayerView>) {
        nsView.play(url: url)
    }

    func makeNSView(context _: Context) -> PlayerNSView {
        PlayerNSView(frame: .zero)
    }

    func dismantleNSView(coordinator _: Coordinator) {
        // not called
    }
}
  

После обновления с видео на изображение аудио видео продолжает воспроизводиться в течение нескольких секунд.

Где я могу указать AVPlayer на паузу? Уведомляет ли VStack видеоплеер?


Обновлено с помощью кода из iUrii:

 struct ContentView: View {
    @State var url: URL?
    @State var playerView: PlayerView?

    private let openFile = NotificationCenter.default.publisher(for: .openFile)

    var body: some View {
        VStack {
            if let view = playerView {
                view
            } else {
                Image(nsImage: NSImage(contentsOf: self.url!)!).resizable().aspectRatio(contentMode: .fit)
            }

        }.onReceive(openFile) { notification in
            url = notification.object as? URL

            if playerView != nil {
                playerView!.player.pause()
                playerView = nil
            }
            if videoExtensions.contains(url!.pathExtension.lowercased()) {
                playerView = PlayerView(url: url!)
            }
        }
    }
}
  

Это отлично работает при переходе от видео к изображению к видео.

Когда я переключаюсь с видео на видео, первое видео будет приостановлено, а аудио из второго видео начнет воспроизводиться, хотя оно не отображается (первое видео все еще видно).


Я решил это, обновив проигрыватель playerView.player.replaceCurrentItem(with: playerItem) вместо замены представления.

Комментарии:

1. dismantleNSView(_:coordinator:) это статическая функция в NSViewRepresentable (по крайней мере, сейчас!), Которая, возможно, и была причиной того, что она не была вызвана.

Ответ №1:

Вы должны управлять своим PlayerNSView с AVPlayer помощью вручную, если хотите контролировать его поведение, например:

 struct PlayerView: NSViewRepresentable {
    let player: AVPlayer
    
    init(url: URL) {
        self.player = AVPlayer(url: url)
    }
    
    func updateNSView(_ nsView: AVPlayerView, context: NSViewRepresentableContext<Self>) {
    }

    func makeNSView(context: NSViewRepresentableContext<Self>) -> AVPlayerView {
        let playerView = AVPlayerView(frame: .zero)
        playerView.player = player
        return playerView
    }
}

struct ContentView: View {
    @State var playerView: PlayerView?
    
    var body: some View {
        VStack {
            if let view = playerView  {
                view
            } else {
                Image(nsImage: NSImage(named: "iphone12")!)
            }
            
            Button("Toogle") {
                if playerView == nil {
                    let url = URL(string: "https://www.apple.com/105/media/us/iphone-12-pro/2020/e70ffbd8-50f1-40f3-ac36-0f03a15ac314/films/product/iphone-12-pro-product-tpl-us-2020_16x9.m3u8")!
                    playerView = PlayerView(url: url)
                    playerView?.player.play()
                }
                else {
                    playerView?.player.pause()
                    playerView = nil
                }
            }
        }
    }
}