macOSアプリのSwiftUIで動画を再生するAVPlayerViewを使いたい場合は、NSViewRepresentableに適合するViewを自分で用意する必要がある。
import SwiftUI
import AVKit
struct PlayerView: NSViewRepresentable {
@Binding var player: AVPlayer
func updateNSView(_ NSView: NSView, context: NSViewRepresentableContext<PlayerView>) {
guard let view = NSView as? AVPlayerView else {
debugPrint("unexpected view")
return
}
view.player = player
}
func makeNSView(context: Context) -> NSView {
return AVPlayerView(frame: .zero)
}
}
makeNSView
でAVPlayerView
の実体を作った後、updateNSView
内でplayer
をセットする。
struct ContentView: View {
@ObservedObject var videoItem: VideoItem = VideoItem()
var body: some View {
VStack {
if videoItem.playerItem != nil {
PlayerView(player: $videoItem.player)
}
Button(action: {
let panel = NSOpenPanel()
panel.allowedFileTypes = [kUTTypeMovie as String]
DispatchQueue.main.async {
if panel.runModal() == .OK {
guard let url = panel.url else {
return
}
self.videoItem.open(url)
}
}
}) {
Text("Open")
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
View側では、player
をBindする。
ファイルを読み直した際に、複数のAVPlayer
が生成されることを防ぐため、AVPlayer
自体は一つのインスタンスを使い回し、replaceCurrentItem
でplayerItem
を切り替えていく形で実装するとシンプルになる。
import SwiftUI
import AVFoundation
class VideoItem: ObservableObject {
@Published var player: AVPlayer = AVPlayer()
@Published var playerItem: AVPlayerItem?
func open(_ url: URL) {
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
self.playerItem = playerItem
player.replaceCurrentItem(with: playerItem)
}
}