播放器

对于资源的播放,你应使用AVPlayer类。你可以使用AVPlayerItem实例去管理整个资源的显示状态,使用AVPlayerIteamTrack类去管理单独任务的显示状态。你可以使用AVPlayerLayer类进行显示

播放资源

player是你用来管理一个资源播放装置的控制类,比如开始播放、结束播放,特定时间的情况等。你可以使用一个AVPlayer实例去播放一个单独的资源。你可以使用一个AVQueuePlayer(AVPlayer的子类)类去播放一个资源队列。在macOS,你可以选择使用AVKit框架下的AVPlayerView播放视频。
…..

操作不同类型的资源

你配置一个资源播放装置的的方式也许取决于你想要播放资源的种类。总的来说,这有两种类型:文件资源,比如本地文件、摄像资源、媒体库等;流文件,流媒体文件、网络视频等

加载和播放文件资源,这有几个步骤来播放文件资源:
  • 使用AVURLAsset创建资源
  • 创建AVPlayItem实例使用资源
  • 通过AVPlayer与item进行绑定
  • 等待直到item的状态特征指示为准备播放(你可以使用key-value监听状态的改变)
创建和准备流媒体播放装置。

通过使用URL初始化一个AVPlayerItem实例(你不能直接使用AVAsset实例去替代流媒体文件)

let url = NSURL("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")
videoItem = AVPlayerItem(URL:url)   self.playerItem.addObserver(self,forKeyPath:"status",options:.New,context:nil) //监听状态
videoplayer = AVPlayer(playerItem:self.playerItem)

当你将playerItem和player绑定时,它就变成了准备播放状态,当它变成了准备播放状态,playerItem就会创建一个AVAsset和AVAssetTrack实例,你可以使用这些检查流媒体的内容。
你可以获取视频时长,通过playItem的duration属性,当视频变成了准备播放状态时,这个属性就会拿到视频的正确时长。
你可以监听状态视频播放状态,例如上面代码。

播放单独资源

videoplayer.play() //播放
播放速度
videoplayer.rate = 2 //速度

值为1表示正常的播放速度,将速度设为0就等同于暂停。它也支持反向播放,通过将速度设为一个负值,你通过设置canPlayReverse属性来设定支持反向播放的状态,它默认的速度是-1,canPlaySlowReverse属性的速度在0到-1之间,canPlayFastReverse属性的速度是小于-1的值。

重新定位播放头

通过使用seekToTime:可以将播放移动到特定的时间:

let fiveSecondsIn = CMTimeMake(30, 1)//当前第20帧,每秒1帧,当前播放时间20/1
        videoplayer.seekToTime(fiveSecondsIn)//跳到fiveSecondsIn

然而,seekToTime:方法更偏向于性能而非精确。如果你想非常精确地跳转到某个进度,你应该使用seekToTime:toleranceBefore:toleranceAfter:

videoplayer.seekToTime(fiveSecondsIn, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

当播放完成之后,播放头正好是停留在结束的位置,即使你使用play也没有任何作用,为了让视频播放完之后重新回到开始的位置,我们可以在通知中心注册一个AVPlayerItemDidPlayToEndTimeNotification通知。在通知的回调方法中,你可以通过seekToTime:将参数设为kCMTimeZero。

//监听播放结束
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FCFAVPlayerController.playItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: videoItem)

func playItemDidReachEnd(notifacation:NSNotification) {
    videoplayer.seekToTime(kCMTimeZero)
}

播放多个资源

你可以使用AVQueuePlayer播放一个资源队列,AVQueuePlayer是AVPlayer的子类,初始化一个AVQueuePlayer:

let playItem1 = AVPlayerItem(URL:NSURL(string: "http://tsmusic128.tc.qq.com/37023937.mp3")!)
let playItem2 = AVPlayerItem(URL:NSURL(string:"http://down.treney.com/mov/test.mp4")!)

let items = NSArray(array: [playItem1,playItem2])
let queuePlayer = AVQueuePlayer(items: items as! [AVPlayerItem])
let layer = AVPlayerLayer(player: queuePlayer)
layer.frame = CGRectMake(20, 200, CGRectGetWidth(self.view.frame)-40, 200)
layer.backgroundColor = UIColor.blueColor().CGColor
self.view.layer.addSublayer(layer)
queuePlayer.play()

你可以使用play()播放资源队列,就像AVPlayer一样,队列按顺序播放每一个资源,如果你想播放在一个资源,你可以使用advanceToNextItem。
你同样可以使用insertItem:afterItem:,removeItem:,removeAllItem:修改资源队列。当你要增加一个新的资源的时候,你最好使用canInsertItem:afterItem:先判断队列是否可以插入新的资源,第二个参数里传入nil则是测试新资源追加到队列末尾。

let playNewItem = AVPlayerItem(URL:NSURL(string:"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!)

if queuePlayer.canInsertItem(playNewItem, afterItem: nil) {
    queuePlayer.insertItem(playNewItem, afterItem: nil)
 }

监听播放

你可以监听当前正在播放的资源的呈现状态及播放项目的各个方面,这对于那些无法直接控制下的状态变化非常有用,比如说:
* 如果用户使用多线程切换不同的app时,播放器的速度将有可能降至0
* 如果正在播放的是一个远程媒体,随着越来越多的数据可用item的loadedTimeRange和seekableTimeRange属性将会改变。这些属性将会告诉你资源的哪些时间段是可用的。
* 播放器的currentItem属性会随着新的item创建而改变
* 播放的过程中item的tracks属性可能会随着播放改变,一般是当内容有多种转码方式的时候,当编码方式切换的时候tracks会改变
* 播放器、item的status可能会改变如果播放失败或者其他一些原因
你可以使用key-value监听这些属性的改变。

监听状态的改变

当播放器或者item的状态改变时,它会发出一个通知。如果一个资源出于某些原因无法播放,那么status就会改变成AVPlayerStatusFailed或者AVPlayerItemStatusFailed,这种状态时,属性error将会有关于为什么不能播放的描述。
AV Foundation没有说明返回的通知是在什么线程,如果你想要更新UI,你必须确保任何相关的ui代码是在主线程中,例如:

//监控状态属性,
 videoItem.addObserver(self, forKeyPath: "status", options: .New, context:nil)
 //监控网络加载情况的属性
 videoItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .New, context: nil)



override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    let playItem:AVPlayerItem = object as! AVPlayerItem
    if keyPath == "status" {

        if playItem.status == AVPlayerItemStatus.Failed {
            let error = playItem.error
            print(error)
            return
        }


        print("正在播放...,视频总长\(CMTimeGetSeconds(playItem.duration))")
    }else if keyPath == "loadedTimeRanges" {
        let array = playItem.loadedTimeRanges
        let timeRange = array.first?.CMTimeRangeValue //本次缓冲时间范围
        let startSecondes = CMTimeGetSeconds((timeRange?.start)!);
        let durationSeconds = CMTimeGetSeconds((timeRange?.duration)!)
        let totalBuffer = startSecondes + durationSeconds //缓冲总长度
        print("缓冲总长度 \(totalBuffer)")
    }

}

追踪显示状态

当资源有显示内容的话,你可以获取一个AVPlayerLayer的readyForDisplay属性通知

追踪时间

你可以使用addPeriodicTimeObserverForInterval:queue:usingBlock:或者addBoundaryTimeObserverForTimes:queue:usingBlock:去追踪播放进度。这样你就知道已经完成的时间和剩余时间,并且同步操作更新UI。

  • addPeriodicTimeObserverForInterval:queue:usingBlock:这个方法传入的是一个CMTime结构时间,每隔这个时间段,block会回调一次,开始和结束的时候,也会回调一次。
  • addBoundaryTimeObserverForTimes:queue:usingBlock:这个是传入了一个CMTime结构的数组,当播放到数组里的时间时,block会回调一次。
    你同样可以使用removeTimeObserver:取消这个观察者。

对于这两个方法,AVFoundation不会保证每次时间点到了都会回调block,如果前面的block没有执行完的时候,下一次就不会回调。所以应该保证block不会太耗时。

//追踪时间
    let durations = CMTimeGetSeconds(asset.duration)
    let firstTrack = CMTimeMakeWithSeconds(durations/3.0, 1)
    let secondTrack = CMTimeMakeWithSeconds(durations*2.0/3.0, 1)
    let times = [NSValue(CMTime: firstTrack),NSValue(CMTime:secondTrack)]
    self.videoplayer.addBoundaryTimeObserverForTimes(times, queue: nil) { 
        print("xxxxx")
    }