15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift 動画再生をAVPlayerでDIYする

Last updated at Posted at 2020-11-28

動画を再生する方法、二通りを試しましたのでメモしておきます。
・AVPlayerViewControllerをそのまま使用
 ソースコード量が少ない、便利
・AVPlayerでDIY実装
 再生/停止ボタン、ProgressBar、Silder、TimeすべてDIY可能

実装する前に、httpのURL動画を扱うために、info.plistをOpen as source codeで開いて
の箇所で以下を追加します。

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

・AVPlayerViewController
 AVKitをImportする必要がある
 URLはネット動画のURL
 ローカル動画のfilePathを使います。

let videoURL = URL(fileURLWithPath: "/Users/yourname/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4")
    let videoURL = URL(string: "http://0000.mp4")
    let player = AVPlayer(url: videoURL!)
    let playerViewController = AVPlayerViewController()
    playerViewController.player = player
    self.present(playerViewController, animated: true) {
        playerViewController.player!.play()
    }

完成イメージ縦画面
Simulator Screen Shot - iPhone 8 Plus - 2020-11-27 at 14.56.58.png
横画面
Simulator Screen Shot - iPhone 8 Plus - 2020-11-27 at 14.57.08.png

AVPlayerでDIYする場合、以下三つのパーツが欠かせません。
・AVPlayerItem どの動画を再生するか、対象やデータを管理したりする。
・AVPlayer 再生、停止などの操作系。
・AVPlayerLayer 動画を表示する役割。

いろいろなパーツをDIYすることを考えると、PlayerViewを作り、
ViewControllerでPlayerViewを使うようにします。

では、まずPlayerViewを作ります。
Layoutはxibファイル内で制御します。

import UIKit
import AVFoundation

class PlayerView: UIView {

    var playerLayer:AVPlayerLayer?
    
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer?.frame = self.bounds
    }
}

次にStoryBoardでViewControllerを作り、新規ViewをPlayerViewタイプに指定します。
AVPlayerItem、AVPlayer、AVPlayerLayerも作ります。

class ViewController: UIViewController {

    var playerItem:AVPlayerItem!
    var avplayer:AVPlayer!
    var playerLayer:AVPlayerLayer!
    
    @IBOutlet var playerView: PlayerView!
}

次にViewDidLoad(画面表示してからすぐ再生)の中にどんどん追加していきます。

override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = URL(fileURLWithPath: "/Users/name/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4")

        playerItem = AVPlayerItem(url: url) //誰を再生するか決める
        
        // 状態監視
        playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil)
        playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)

        self.avplayer = AVPlayer(playerItem: playerItem)
        playerLayer = AVPlayerLayer(player: avplayer)
        // 表示モードの設定
        playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
        playerLayer.contentsScale = UIScreen.main.scale

        self.playerView.playerLayer = self.playerLayer
        self.playerView.layer.insertSublayer(playerLayer, at: 0)
    }

    //画面消える時にremove
    deinit{
        playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
        playerItem.removeObserver(self, forKeyPath: "status")
    }
    
   //監視イベント
   //Unknown 、ReadyToPlay 、 Failed状態があり、readyToPlayの際のみ再生
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let playerItem = object as? AVPlayerItem else { return }
            if keyPath == "loadedTimeRanges"{
                // TODO
            }else if keyPath == "status"{
                if playerItem.status == AVPlayerItem.Status.readyToPlay{
                    self.avplayer.play()
                }else{
                    print("error")
                }
            }
    }

これで再生できるはずです。でも、ボタンもProgressBarも何もないので引き続き実装に進みます。

●Time
PlayerViewにてCurrentTimeとTotalTimeを表示するtimeLabelを作成
ViewControllerにて秒数をStringへ変換するメソッドを作成

func formatPlayTime(secounds:TimeInterval)->String{
        if secounds.isNaN{
            return "00:00"
        }
        let Min = Int(secounds / 60)
        let Sec = Int(secounds.truncatingRemainder(dividingBy: 60))
        return String(format: "%02d:%02d", Min, Sec)
    }

常に時間の変化を表示するため、CADisPlayLinkを生成して使います。
動画のコマごとにCADisPlayLinkが呼ばれます。

self.link = CADisplayLink(target: self, selector: #selector(update))
self.link.add( to: RunLoop.main, forMode: RunLoop.Mode.default)

updateの中身はこのように書きます。これで時間が表示できるようになります。

@objc func update(){
    let currentTime = CMTimeGetSeconds(self.avplayer.currentTime())
    let totalTime   = NSTimeInterval(playerItem.duration.value) / NSTimeInterval(playerItem.duration.timescale)
    let timeStr = "\(formatPlayTime(currentTime))/\(formatPlayTime(totalTime))" 
    playerView.timeLabel.text = timeStr 
}

●ProgressBar
次に再生の進捗によってProgressBarも動くようにUISilderを使います。
StoryBoardでUISilderを追加したら、以下のように設定します。スクリーンショット 2020-11-28 8.56.23.png

SilderのPinのImageを変えるため、以下を書きます。

@IBOutlet weak var slider: UISlider!{
    didSet{
        slider.setThumbImage(UIImage(systemName: "bolt.fill"), for: UIControl.State.normal)
    }
}

Pinが動くようにUpdateメソッドに以下を追加します。

self.testPlayerView.slider.value = Float(currentTime/totalTime)

●ProgressBarの動作
この時点ではProgressBarをドラッグしたりできませんので次に動作の処理をします。
awakeFromNibの中で追加します。念のため指を離した時は三つイベントを監視します。

// タップした時
slider.addTarget(self, action: #selector(sliderTouchDown), for: UIControl.Event.touchDown)
// 指を離した時
slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpOutside)
slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpInside)
slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchCancel)

ドラッグしている間では動画の再生進捗を変え続けたくないので、sliding を追加します。
ここでProtocalが登場します。

protocol TestPlayerViewDelegate:NSObjectProtocol {
    func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider)
}

PlayerViewの中でdelegateを使います。

weak var delegate: TestPlayerViewDelegate?

前のsliderTouchDown、sliderTouchUpOutのメソッドを書きます。

@objc func sliderTouchDown(slider:UISlider){
    self.sliding = true
}

@objc func sliderTouchUpOut(slider:UISlider){
    delegate?.testPlayer(playerView: self, sliderTouchUpOut: slider)
}

そしてViewControllerの中で実際に指を離した時の処理を書きます。

extension ViewController: TestPlayerViewDelegate{
    
    func testPlayer(playerView: PlayerView, sliderTouchUpOut slider: UISlider) {
        let duration = slider.value * Float(CMTimeGetSeconds(self.avplayer.currentItem!.duration))
        let seekTime = CMTimeMake(value: Int64(duration), timescale: 1)
        // 位置を特定
        self.avplayer.seek(to: seekTime, completionHandler: { (b) in
            // sliding状態を戻す
            playerView.sliding = false
        })
    }
}

ViewControllerのviewDidLoadでdelegateを忘れずに

self.playerView.delegate = self

UpdateメソッドのSilderが動く処理にもsliding状態の判断を追加します。
これでProgressBarはドラッグできるようになります。

if !self.playerView.sliding{
    // 播放进度
    self.playerView.slider.value = Float(currentTime/totalTime)
}

●PlayAndPauseボタン
最後に再生/停止ボタンの処理を書きます。
Sliderの処理と似ていています。StoryBaordでボタンを設置、Layoutを設定します。
awakeFromNibの中で以下を追加します。

playAndPauseBtn.addTarget(self, action: #selector(playAndPause) , for: UIControl.Event.touchUpInside)

メソッドを追加します。
再生と停止の処理もDelegateの中で書きます。

@objc func playAndPause(btn:UIButton){
    let tmp = !playing
    playing = tmp 

    if playing {
        playAndPauseBtn.setImage(UIImage(systemName: "stop.circle"), for: UIControl.State.normal)
    }else{
        playAndPauseBtn.setImage(UIImage(systemName: "play.circle"), for: UIControl.State.normal)
    }

    delegate?.testPlayer(playerView: self, playAndPause: btn)
}
protocol TestPlayerViewDelegate:NSObjectProtocol {
    func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider)
    func testPlayer(playerView:PlayerView,playAndPause playBtn:UIButton)
}

ViewControllerでメソッドを書きます。これで再生や停止ができるようになります。

func testPlayer(playerView: PlayerView, playAndPause playBtn: UIButton) {
    if !playerView.playing{
        self.avplayer.pause()
    }else{
        if self.avplayer.status == AVPlayer.Status.readyToPlay{
            self.avplayer.play()
        }
    }
}

完成イメージ
Simulator Screen Shot - iPod touch (7th generation) - 2020-11-28 at 09.51.02.png
Simulator Screen Shot - iPod touch (7th generation) - 2020-11-28 at 09.51.08.png

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?