SwiftUIでのPicture in Picture (PiP)の実装方法を説明する。
結論
以下の具体例の手順で実装する。
具体例
App概要
動画を視聴するをタップすると動画を開始する。
動画再生中にホーム画面に行くとPicture in Picture (PiP)で再生する。PC上のシミュレーターではPicture in Picture (PiP)できないので以下の画像は実機で実験した画像である。
作成方法
XcodeでTARGETS -> Signing & Capabilities -> Background Modes の Audio, AirPlay, and Picture in Pictureをチェックする。
XcodeでTARGETS -> Build Phases -> Copy Bundle Resources を開きmp4動画をドラッグ&ドロップする。
Copy items if needed、Create groupsをチェックしてfinish。
以下のコードを記述する。
import SwiftUI
import AVKit
import Combine
let my動画 = AVModel(title: "地球の動画",
url: Bundle.main.url(forResource: "地球の動画",
withExtension: "mp4")!)
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("▶ 動画を視聴する", destination: 動画ビュー(動画: my動画))
}
}
}
struct 動画ビュー: View {
@StateObject private var viewModel = AVViewModel()
let 動画: AVModel
var body: some View {
AVView(avViewModel: viewModel)
.onAppear {
viewModel.avModel = 動画
viewModel.player.play()
}
}
}
// View
struct AVView: UIViewControllerRepresentable {
@ObservedObject var avViewModel: AVViewModel
func makeUIViewController(context: Context) -> AVPlayerViewController {
let vc = AVPlayerViewController()
vc.player = avViewModel.player
vc.canStartPictureInPictureAutomaticallyFromInline = true
return vc
}
func updateUIViewController(_ uiViewController: AVPlayerViewController,
context: Context) { }
}
// ViewModel
class AVViewModel: ObservableObject {
@Published var avModel: AVModel?
let player = AVPlayer()
private var cancellable: AnyCancellable?
init() {
setAudioSessionCategoryToPlayback()
cancellable = $avModel.sink(receiveValue: {
guard let model = $0 else { return }
self.player.replaceCurrentItem(with: AVPlayerItem(url: model.url))
})
}
func setAudioSessionCategoryToPlayback() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback)
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}
}
// Model
struct AVModel {
let title: String
let url: URL
}
解説
Model
- 動画のタイトルとURLを入れる箱のstructを準備する。
- このstructはMVVM (Model–view–viewmodel)アーキテクチャのModelを意識してAVModelと名付ける。
// Model
struct AVModel {
let title: String
let url: URL
}
ViewModel
- aVModelが変化する(aVModelの動画タイトルとURLが設定される)のを.sinkで受け取る。
- .sinkを受け取ったら、動画プレイヤーplayerが再生する対象をaVModelのurlに設定する。
- AudioSessionCategoryは.Playbackにしないといけないことになっているので設定するだけ。
- このclassはMVVM (Model–view–viewmodel)アーキテクチャのViewModelを意識してAVViewModelと名付ける。
// ViewModel
class AVViewModel: ObservableObject { // ? 4
@Published var avModel: AVModel?
let player = AVPlayer()
private var cancellable: AnyCancellable?
init() {
setAudioSessionCategoryToPlayback() // ? 3
cancellable = $avModel.sink(receiveValue: { // ? 1
guard let model = $0 else { return }
self.player.replaceCurrentItem(with: AVPlayerItem(url: model.url)) // ? 2
})
}
func setAudioSessionCategoryToPlayback() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback)
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}
}
View
- UIViewControllerRepresentable準拠のstructを準備する。
- canStartPictureInPictureAutomaticallyFromInline = true でPicture in Picture を有効にする。
- このstructはMVVM (Model–view–viewmodel)アーキテクチャのViewを意識してAVViewと名付ける。
// View
struct AVView: UIViewControllerRepresentable { // ? 1
@ObservedObject var avViewModel: AVViewModel
func makeUIViewController(context: Context) -> AVPlayerViewController {
let vc = AVPlayerViewController()
vc.player = avViewModel.player
vc.canStartPictureInPictureAutomaticallyFromInline = true // ? 2
return vc
}
func updateUIViewController(_ uiViewController: AVPlayerViewController,
context: Context) { }
}
最後にAVModel、AVViewModel、AVViewを使って動画ビューを構築する。
struct 動画ビュー: View {
@StateObject private var viewModel = AVViewModel()
let 動画: AVModel
var body: some View {
AVView(avViewModel: viewModel)
.onAppear {
viewModel.avModel = 動画
viewModel.player.play()
}
}
}
この動画ビューをContentViewから見ることができるようにする。
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("▶ 動画を視聴する", destination: 動画ビュー(動画: my動画))
}
}
}
動画はAppに入れておいたmp4ファイルを設定する。
let my動画 = AVModel(title: "地球の動画",
url: Bundle.main.url(forResource: "地球の動画",
withExtension: "mp4")!)
まとめ
SwiftUIでのPicture in Picture (PiP)の実装方法を説明した。
コメント