🙄

[Swift] iOSのVideoPlayerで保存した動画を自動再生せず読み込む

2023/02/13に公開

概要

iOSアプリでiPhone内の動画を再生したい。
WKWebViewに読み込ませると、画面表示時に自動再生してしまう…。
WKWebViewConfigurationmediaTypesRequiringUserActionForPlaybackを設定してみたけど効かない…。
自前で作るにもAVPlayerは実装コスト高そう…。

そんなときはAVPlayerViewControllerVideoPlayerを利用できます。

サンプルコード

1. UIKit で AVPlayerViewController を使う場合

VideoPreviewViewController.swift
import AVKit
import UIKit

class VideoPreviewViewController: UIViewController {
    let url: URL

    init(url: URL) {
        self.url = url
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let playerViewController = AVPlayerViewController()
        let playerItem = AVPlayerItem(url: url)
        let player = AVPlayer(playerItem: playerItem)
        playerViewController.player = player
        playerViewController.showsPlaybackControls = true

        self.addChild(playerViewController)
        self.view.addSubview(playerViewController.view)
        playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
        playerViewController.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
        playerViewController.view.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
        playerViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        playerViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
    }
}

2. SwiftUI で VideoPlayer を使う場合

VideoPreviewView.swift
import AVKit
import SwiftUI

struct VideoPreviewView: View {
    private let endPublisher = NotificationCenter.default.publisher(for: NSNotification.Name.AVPlayerItemDidPlayToEndTime)
    @State private var isPlaying = false
    
    var url: URL

    var body: some View {
        VStack {
            let player = AVPlayer(url: url)
            VideoPlayer(player: player)
                .onReceive(endPublisher) { _ in
                    player.seek(to: CMTimeMakeWithSeconds(0, preferredTimescale: Int32(NSEC_PER_SEC)), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) // player.seek(to: .zero) でもよい
                    isPlaying = false
                }
            Button {
                if isPlaying {
                    player.pause()
                } else {
                    player.play()
                }
                isPlaying.toggle()
            } label: {
                Image(systemName: isPlaying ? "stop" : "play")
		    .padding()
            }
        }
    }
}

3. SwiftUI の VideoPlayer を UIKit から使う場合

MyViewController.swift
let videoVC = UIHostingController(rootView: VideoPreviewView(url: url))
self.addChild(videoVC)
self.view.addSubview(videoVC.view)
videoVC.view.translatesAutoresizingMaskIntoConstraints = false
videoVC.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
videoVC.view.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
videoVC.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
videoVC.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true

4. UIKit の AVPlayerViewController を SwiftUI から使う場合(URLの外部注入を含む)

UIKitVideoPreviewView.swift
struct UIKitVideoPreviewView: UIViewControllerRepresentable {
    @EnvironmentObject private var environment :VideoPlayerEnvironment
    typealias UIViewControllerType = VideoPreviewViewController

    func makeUIViewController(context: Context) -> VideoPreviewViewController {
        return VideoPreviewViewController(url: environment.url)
    }

    func updateUIViewController(_ uiViewController: VideoPreviewViewController, context: Context) {
        // Updates the state of the specified view controller with new information from SwiftUI.
    }
}
ContentView.swift
struct ContentView: View {
    @EnvironmentObject private var environment :VideoPlayerEnvironment
    @State private var isUIKitPresented = false

    var body: some View {
        VStack {
            Button("UIKit") {
                isUIKitPresented.toggle()
            }
            .sheet(isPresented: $isUIKitPresented) {
                UIKitVideoPreviewView()
            }
        }
        .padding()
    }
}
VideoPlayerEnvironment.swift
class VideoPlayerEnvironment: ObservableObject {
    var url = URL(fileURLWithPath: "/Your/Path/To/Video.mp4")
}
VideoPlayerSampleApp.swift
@main
struct VideoPlayerSampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(VideoPlayerEnvironment())
        }
    }
}

実行結果

UIKit版(4.のやり方で表示)

SwiftUI版

全画面化する左上のボタンはUIKit版だけ出てますね。

VideoPlayer側にも何かオプションが生えているかもしれませんが未調査です。

参考:https://developer.apple.com/documentation/avkit/videoplayer

Discussion