AVFoundation

AVPlayerの使い方を確認してみる

swift-avplayer-sample

AVPlayerの使い方を確認してみる

iOSではどうやって動画はどうやって再生するのか?

弊社のアプリの中で動画を再生するところがあるのですが、動画の再生の実装をしたことなく、 実装方法がわからなかったので、調べてみました。
動画を再生する方法は色々ありますが、今回は、AvPlayerの基本的な使い方、及びできることをまとめてみました。 今回いろいろ実装したサンプルアプリは、 こちらから確認できます 実装の詳細については、こちらのリポジトリを確認してください。


今回確認したいこと
  1. HLS(mp4)形式の動画を再生・停止する
  2. 再生状況を取得する
  3. 再生速度を変更する
  4. スライダーで指定の秒数で再生するようにする
  5. 指定の秒数早送り・巻き戻しする
  6. 音量を調整する

 

1. HLS(mp4)形式の動画を再生・停止する

そもそもHLSとは、Appleが開発した動画再生の規格です。詳しくは、こちらなどのサイトを確認してみてください。 まずは上記のアプリで実装している箇所を記載します。

private let avPlayer = AVPlayer()
private let avPlayerLayer = AVPlayerLayer()
private var avPlayerItem: AVPlayerItem?

private func setupPlayer() {
    // ①AVAssetを作成する
    let avAsset = AVAsset(url: URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!)
    // ②AvAssetをもとに、AVPlayerItemを作成する
    let avPlayerItem = AVPlayerItem(asset: avAsset)
    self.avPlayerItem = avPlayerItem
    // ③avPlayerItemをavPlayerにセットする
    avPlayer.replaceCurrentItem(with: avPlayerItem)
    // ④AVPlayerLayerをViewに設定する 
    playerView.layer.addSublayer(avPlayerLayer)
    // ⑤AVPlayerをAVPlayerLayerに設定する
    avPlayerLayer.player = avPlayer
    avPlayerLayer.frame = playerView.frame
    avPlayerLayer.videoGravity = .resizeAspect
    avPlayerLayer.needsDisplayOnBoundsChange = true
}

別の設定のコードも含まれてますが、基本的な手順としては、

  1. AVAssetを作成する
  2. AvAssetをもとに、AVPlayerItemを作成する
  3. avPlayerItemをavPlayerにセットする
  4. AVPlayerLayerをViewに設定する
  5. AVPlayerをAVPlayerLayerに設定する

をした後に、

avPlayer.play()

を実行すると、設定したサンプル動画が動画が再生されます。 mp4の動画を再生したい場合は、URLをmp4のものに変更すれば、上記と同じ実装で再生することができます。

2. 再生状況を取得する

動画を再生しようとした時に、1.のコードを実行した後、すぐに再生できるわけではありません。 通信環境にもよりますが、最初にある程度ダウンロードした状態にならないと、実際に再生することはできません。
そこで、再生できる状態になっているかどうかのステータスを取得する方法を確認します。 このステータスは、AvPlayerのenumのstatusプロパティを確認し、これが.readyToPlayになっていれば、 再生することができます。
なおこのアプリでは検証のため、このプロパティの変化を確認できるように、そのステータスをLabelに出力していますが、 KVO(Key Value Observing)を使って、ステータスの変化を監視しています。

@IBOutlet private weak var statusLabel: UILabel!
private var observers: [NSKeyValueObservation] = []

observers.append(avPlayer.observe(\.status, options: .new, changeHandler: { [unowned self] (avPlayer, value) in
  // valueを使うと、nilになる
  switch avPlayer.status {
  case .readyToPlay: self.statusLabel.text = "Ready to play"
  case .failed: self.statusLabel.text = "Failed"
  case .unknown: self.statusLabel.text = "unknown"
  @unknown default: fatalError()
  }
}))

(本当はvalueに変更された後の値が流れてくるはずだが、なぜかnilになるので、avPlayer.statusの値を直接取得している) また再生中/停止中のステータスは、avPlayer.timeControlStatusで取得できますが、statusと同じようにKVOで変更を監視しています。

@IBOutlet private weak var timeControlStatusLabel: UILabel!

switch avPlayer.timeControlStatus {
  case .playing: self.timeControlStatusLabel.text = "Playing"
  case .paused: self.timeControlStatusLabel.text = "Paused"
  case .waitingToPlayAtSpecifiedRate: self.timeControlStatusLabel.text = "WaitingToPlayAtSpecifiedRate"
  @unknown default: fatalError()
}

3. 再生速度を変更する

再生速度は、avPlayer.rateから確認することができます。 この値を変更することで、再生速度を変更することができます。 1倍速の時は、1に設定し、2倍速にしたい時は、2に設定します。 このアプリでは、segmentの切り替えをトリガーに、0.5/1/2倍速を切り替えられるようにしています。

@IBAction private func onPlaySpeedValueChanged(_ sender: UISegmentedControl) {
  if sender.selectedSegmentIndex == 0 {
    avPlayer.rate = 0.5
  } else if sender.selectedSegmentIndex == 1 {
    avPlayer.rate = 1
  } else if sender.selectedSegmentIndex == 2 {
    avPlayer.rate = 2
  }
}

4. スライダーで指定の秒数で再生するようにする

これはアプリスクショのControlセクションにあるSliderを動かすと、その進捗具合に合わせて、動画の再生時間を変更させる機能になります。 AVPlayerではこのあたりの機能を使う時は、CMTimeなどのstructを使います。(参考)

@IBAction func onDurationSliderValueChanged(_ sender: UISlider, forEvent event: UIEvent) {
  guard let touchEvent = event.allTouches?.first else { return }
  switch touchEvent.phase {
  case .began:
    isSliderDragging = true
  case .ended:
    // ①スライダーの値から、目標の再生時間を求める
    let seconds = Double(sender.value) * itemDuration
    let timeScale = CMTimeScale(NSEC_PER_SEC)
    // 目標の再生時間から、実際に再生時間を変更するための、`CMTime`を生成する。
    let time = CMTime(seconds: seconds, preferredTimescale: timeScale)
    changePosition(time: time)
    isSliderDragging = false
  case .cancelled:
    isSliderDragging = false
  default: break
  }
}

private func changePosition(time: CMTime) {
  // timeの値をもとに、SliderのUIを更新する
  updateDurationSlider(time: time)
  let rate = avPlayer.rate
  // 時間変更中は、一時的に停止する
  avPlayer.rate = 0
  avPlayer.seek(to: time) { [weak self] completed in
  if !completed { return }
    // 変更が完了したら、再び再生する
    self?.avPlayer.rate = rate
  }
}

5. 指定の秒数早送り・巻き戻しする

この機能は、4.の機能と同じように、現在の再生時間から指定時間早送り、巻き戻しするものです。 4.の実装と共通している箇所もあります。 このアプリでは検証のため、10/15/30秒を指定できるようにしています。

private var offsetDefaultTime: Double = 15

@IBAction func back(_ sender: Any) {
  offsetTime(offset: -offsetDefaultTime)
}

@IBAction func forward(_ sender: Any) {
  offsetTime(offset: offsetDefaultTime)
}

private func offsetTime(offset: Double) {
  let timeScale = CMTimeScale(NSEC_PER_SEC)
  // 加減したい時間(秒数)をもとに、`CMTime`のstructを作成する
  let rhs = CMTime(seconds: offset, preferredTimescale: timeScale)
  // 現在のtime(avPlayer.currentTime())に、offset分の秒数を加減する。
  let time = CMTimeAdd(avPlayer.currentTime(), rhs)
  changePosition(time: time)
}

6. 音量を調整する

ここではスライダーの値に合わせて、動画の音量を調整する実装を行います。 avPlayer.volume(最小値が0、最大値が1)の値を変更することで、動画の音量を変更することができます。

@IBAction func onVolumeSliderValueChanged(_ sender: UISlider) {
  setVolume(volume: sender.value)
}

private func setVolume(volume: Float) {
  avPlayer.volume = volume
  volumeSlider.value = volume
}

まとめ

調査と実装をする中で、基本的な使い方は大まかに把握できました。 ただもっと細かい設定など、色々できるので、そこは今後開発などをしながら、少しずつ掴んでいければと思います。

参考資料

+1

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA