Swiftでショートムービ(動画)を再生してみる
この記事でできるもの
どうやるの?
動画を再生する方法は現在だと、3つ方法があります。この他にも MPMoviePlayerController
が
ありますが、iOS9以降は Deprecated です。今回はこの内の AVPlayer
を使った実装を行っていき
ます。
- AVPlayerViewController
- AVPlayer => 今回はこれで実装します!
- 正しく書くと
AVPlayerLayer
をlayerClass
とする UIView のサブクラス
- 正しく書くと
- WKWebView
ざっくり手順
動画を表示するための UIView サブクラスを作成します。
layerClass
を Override してAVPlayerLayer
を使用できるようにセットします。- 公式ドキュメント
- https://developer.apple.com/documentation/uikit/uiview/1622626-layerclass
- View の CoreAnimationLayer の作成に使用されるクラス
- デフォルトでは
CALayer
が定義される - この Property は対応する LayerObject を作成するために View の初期化時に一度だけコール される
作成した動画 View で動画を再生できるようにします。
最後に
AVPlayer
を View に渡して動画を再生できるようにします。- AVPlayer を再生する際には下記の3つの種類がありますが、今回はその内の
ストリーミングでの動画再生
で実装していきます。- ストリーミングでの動画再生
- ダウンロードして動画再生
- Bundle Resource にある動画ファイルの再生
- AVPlayer を再生する際には下記の3つの種類がありますが、今回はその内の
実装してみる
動画を表示するためのクラス作成
まずは動画を再生するために、AVPlayerLayer
を layerClass
とする UIView のサブクラスを
作成していきましょう。公式のドキュメント
にもありますが、プロパティとして playerLayer
を持っておくことで後々使う AVPlayer
の取り
回しが楽になります。
class VideoPlayerView: UIView { private var playerLayer: AVPlayerLayer? { return layer as? AVPlayerLayer } // Override UIView property override static var layerClass: AnyClass { return AVPlayerLayer.self } }
VideoPlayerView
の動画を簡単に再生できるようにする
基本的には動画の制御に必要な API は AVPlayer
が全て持ってますが、今回は動画広告のを流すとい
うことで AVPlayer
と 広告ID
を一緒にセットしたいので player プロパティは private(set)
にしておきます。もし、特にそのような事情がなければ player プロパティは private にしなくても大
丈夫です。
isPlaying
に関しては AVPlayer
に特にそれっぽい API が無かったのでプロパティを追加しま
した。
class VideoPlayerView: UIView { private(set) var player: AVPlayer? { get { return playerLayer?.player } set { playerLayer?.player = newValue } } var isPlaying: Bool { guard let player = player else { return false } return player.rate != 0 && player.error == nil } private var playerLayer: AVPlayerLayer? { return layer as? AVPlayerLayer } // Override UIView property override static var layerClass: AnyClass { return AVPlayerLayer.self } private var currentId: Int32? func setPlayer(id: Int32, player: AVPlayer) { currentId = id self.player = player } }
動画 View を使用する View に追加して再生してみる
これで一通り基本的な動画が再生はできるようになりました。
class VideoAdView: UIView { let videoView = VideoPlayerView() override init(frame: CGRect) { super.init(frame: frame) videoView.frame = frame addSubview(videoView) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setPlayer(id: Int32, url: URL) { let player = AVPlayer(url: url) videoView.setPlayer(id: id, player: player) } // 再生 func play() { videoView.player?.play() } // 停止 func pause() { videoView.player?.pause() } // ミュート func mute(isMute: Bool) { videoView.player?.isMuted = isMute } }
余談
今回は動画広告ということで、いくつかの動画を順番に再生したかったので動画の終了イベントを検知でき
るようにしてみます。それとついでに動画の再生回数のイベントも送信したいので、1秒以上再生された場合
にイベントを送信できるように処理を書いていきます。動画の終了イベントに関しては AVPlayer
に
Delegate が用意されているのかと思いましたがそういうわけではありませんでした、😓
class VideoPlayerView: UIView { private(set) var player: AVPlayer? { get { return playerLayer?.player } set { playerLayer?.player = newValue } } private var timeObserverToken: Any? var isPlaying: Bool { guard let player = player else { return false } return player.rate != 0 && player.error == nil } private var playerLayer: AVPlayerLayer? { return layer as? AVPlayerLayer } // Override UIView property override static var layerClass: AnyClass { return AVPlayerLayer.self } private var currentId: Int32? private var stopObserveClosure: (() -> ())? weak var delegate: VideoPlayerViewDelegate? deinit { stopObserveClosure?() } func setPlayer(id: Int32, player: AVPlayer) { stopObserveClosure?() setTimeObserver(player: player) if let currentItem = player.currentItem { stopObserveClosure = observeNotification(currentItem: currentItem) } currentId = id self.player = player } // 動画の再生イベント送信 private func setTimeObserver(player: AVPlayer) { timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main, using: {[weak self] time in guard let self = self, let timeObserverToken = self.timeObserverToken else { return } let seconds = CMTimeGetSeconds(time) if seconds >= 1.0 { // TODO: Add tracking event. self.player?.removeTimeObserver(timeObserverToken) self.timeObserverToken = nil } }) } // 動画の終了イベントを通知 @objc private func playerDidFinishPlaying(_ sender: Any) { guard let id = currentId else { return } // TODO: Handle Player Finish Playing } private func observeNotification(currentItem: AVPlayerItem) -> () -> Void { NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_:)), name: .AVPlayerItemDidPlayToEndTime, object: currentItem) return {[weak self] in guard let self = self else { return } NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: currentItem) } } }