前提・実現したいこと
PHPickerViewControllerで選んだ動画から、urlを取り出し、それを再生したい。
流れ
- PHPickerViewControllerに偏移し、動画が選ばれたら、URLを抽出し、それをPublishSubjectで流す。
*RxSwift関連のDelegateProxyを使っています。おそらくこのurlを渡すことについては問題はないと思いますが一応記述します。
0. そのurlを受け取り、動画を再生するViewControllerに対応するViewModelに渡し、偏移する。
0. 動画を再生するUIView(=PlayVideoView)のセットアップをする。(viewdidload内で)
0. ループ再生がしたいので、AVQueuePlayer、AVPlayerLooperとAVPlayerLayerを初期化そしてそれらのフレームの大きさなどのセットアップをする。(viewdidload内で)
0. PlayVideoView内のactivitiy indicatorのアニメーションをスタート、準備ができたらストップ
0. 動画再生(コード内ではviewdidappear内で行なっていますが、どこでも大丈夫だと思います。ただし該当のViewcontollerに偏移されたら直ぐに再生したいです。)
発生している問題・エラーメッセージ
まず全く再生される気配すらありません。AVPlayer.status, VPlayer.currentItem.statusとAVPlayerItemNewErrorLogEntry, AVPlayerItemFailedToPlayToEndTime, AVPlayerItemPlaybackStalled(これは関係なさそう)にオブザーバーを追加していますがどのオブザーバーも発動しません。そして何らかのエラーメッセージも表示されません。
またコードを直していく過程でAVQueuePlayerがnilになっていたので、それを直しました。よってAVQueuePlayerがnilであることは原因ではありません。
試したこと
別の簡易のProjectを作り同じ動画を選択して、再生したのですがそれは成功したのでURLが再生できないというわけではなさそうです。もしかして何かのタイミングが原因なのかもしません。
参考URL
https://stackoverflow.com/a/58800860
該当のソースコード
Swift
1 2import UIKit 3import AVKit 4import RxSwift 5import RxCocoa 6 7class UploadingVideoViewController: UIViewController, BindableType { 8 9 typealias ViewModelType = UploadingVideoVM 10 var viewModel: UploadingVideoVM! 11 12 var playView: PlayVideoView! 13 let loop = VideoPlayerLooped() 14 15 override func viewDidLoad() { 16 <省略> 17 18 setUpPlayVideoView() 19 20 } 21 22 override func viewDidAppear(_ animated: Bool) { 23 24 self.loop.playVideo(url: self.viewModel.url, inView: self.playView) 25 26 } 27 28 func setUpPlayVideoView() { 29 30 self.playView = PlayVideoView() 31 playView.frame = view.bounds 32 33 setUpBackBtn() 34 setUpAddBtn() 35 setUpSlider() 36 setUpGestureRecognizer() 37 38 view.addSubview(self.playView) 39 40 let interval = CMTime(value: 1, timescale: 2) 41 42 self.videoPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [unowned self] progressTime in 43 44 if let duration = self.videoPlayer?.currentItem?.duration { 45 46 let seconds = CMTimeGetSeconds(progressTime) 47 let durationSeconds = CMTimeGetSeconds(duration) 48 self.playView.slider.value = Float(seconds / durationSeconds) 49 50 } 51 } 52 53 NotificationCenter.default.addObserver(self, selector: #selector(itemNewErrorLogEntry), name: .AVPlayerItemNewErrorLogEntry, object: nil) 54 55 NotificationCenter.default.addObserver(self, selector: #selector(itemFailedToPlayToEndTime), name: .AVPlayerItemFailedToPlayToEndTime, object: nil) 56 57 NotificationCenter.default.addObserver(self, selector: #selector(itemPlaybackStalled), name: .AVPlayerItemPlaybackStalled, object: nil) 58 59 self.videoPlayer?.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil) 60 self.videoPlayer?.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options:[.new, .initial], context: nil) 61 62 63 } 64 65 66 67 func setUpBackBtn() { 68 <省略> 69 self.playView.backBtn.setTitle("", for: .normal)など 70 71 } 72 73 74 func setUpAddBtn() { 75 <省略> 76 77 } 78 79 func setUpSlider() { 80 81 self.playView.slider.rx.controlEvent(.valueChanged) 82 .catch { err in 83 return .empty() 84 } 85 .subscribe(onNext: { [unowned self] in 86 87 if let duration = self.videoPlayer?.currentItem?.duration { 88 89 let totalSeconds = CMTimeGetSeconds(duration) 90 let value = Float64(self.playView.slider.value) * totalSeconds 91 let seekTime = CMTime(value: Int64(value), timescale: 1) 92 93 94 videoPlayer?.seek(to: seekTime, completionHandler: { isCompleted in 95 96 }) 97 } 98 99 }) 100 .disposed(by: viewModel.disposeBag) 101 } 102 103 104 105 fileprivate func setUpGestureRecognizer() { 106 107 <省略> 108 } 109 110 @objc func itemPlaybackStalled() { 111 print("AVPlayerItemNewErrorLogEntry") 112 } 113 114 @objc func itemNewErrorLogEntry() { 115 print("itemNewErrorLogEntry") 116 } 117 118 119 @objc func itemFailedToPlayToEndTime() { 120 print("failed") 121 } 122 func playVideo(url: URL, inView: UIView){ 123 124 let playerItem = AVPlayerItem(url: url) 125 126 videoPlayer = AVQueuePlayer(items: [playerItem]) 127 playerLooper = AVPlayerLooper(player: videoPlayer!, templateItem: playerItem) 128 129 videoPlayerLayer = AVPlayerLayer(player: videoPlayer) 130 videoPlayerLayer!.frame = inView.bounds 131 videoPlayerLayer!.videoGravity = .resizeAspectFill 132 133 inView.layer.addSublayer(videoPlayerLayer!) 134 135 136 137 videoPlayer?.play() 138 139 } 140 141 func remove() { 142 videoPlayerLayer?.removeFromSuperlayer() 143 144 } 145 146 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 147 148 <省略> 149 150if let _ = object as? AVPlayer { 151 152 if keyPath == #keyPath(AVPlayer.currentItem.status) { 153 154 let newStatus: AVPlayerItem.Status 155 if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber { 156 newStatus = AVPlayerItem.Status(rawValue: newStatusAsNumber.intValue)! 157 } else { 158 newStatus = .unknown 159 } 160 if newStatus == .failed { 161 NSLog("Error: (String(describing: self.videoPlayer?.currentItem?.error?.localizedDescription)), error: (String(describing: self.videoPlayer?.currentItem?.error))") 162 } 163 } 164 165 } 166 167 } 168} 169
Swift
1 2class PlayVideoView: UIView { 3 4 5 6 let indicator: UIActivityIndicatorView = { 7 8 let result = UIActivityIndicatorView() 9 <省略> 10 return result 11 }() 12 13 14 @IBOutlet weak var backBtn: UIButton! 15 @IBOutlet weak var addBtn: UIButton! 16 @IBOutlet weak var slider: UISlider! 17 18 19 override init(frame: CGRect) { 20 super.init(frame: frame) 21 <省略> 22 23 } 24 25 required init?(coder aDecoder: NSCoder) { 26 super.init(coder: aDecoder)! 27 loadNib() 28 29 } 30 31 32 <省略> 33 34} 35
Swift
1import Foundation 2import AVKit 3 4class VideoPlayerLooped { 5 6 public var videoPlayer:AVQueuePlayer? 7 public var videoPlayerLayer:AVPlayerLayer? 8 var playerLooper: NSObject? 9 var queuePlayer: AVQueuePlayer? 10 11 func playVideo(url: URL, inView: UIView){ 12 13 let playerItem = AVPlayerItem(url: url) 14 15 videoPlayer = AVQueuePlayer(items: [playerItem]) 16 playerLooper = AVPlayerLooper(player: videoPlayer!, templateItem: playerItem) 17 18 videoPlayerLayer = AVPlayerLayer(player: videoPlayer) 19 videoPlayerLayer!.frame = inView.bounds 20 videoPlayerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill 21 22 inView.layer.addSublayer(videoPlayerLayer!) 23 24 if let indicator = inView.subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView { 25 26 indicator.stopAnimating() 27 28 } 29 30 videoPlayer?.play() 31 32 } 33 34 func remove() { 35 videoPlayerLayer?.removeFromSuperlayer() 36 37 } 38} 39
補足情報(FW/ツールのバージョンなど)
Xcode 13.1 Swift 5
あなたの回答
tips
プレビュー