動画を再生する方法、二通りを試しましたのでメモしておきます。
・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()
}
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を追加したら、以下のように設定します。
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()
}
}
}