avplayer swift

2016-12-07 15:42:53 u011315300 阅读数 2116

## AVPlayer的简单使用 ##
自己写得并不好(刚学swift,语法可能有点奇怪,偏向OC),只是试试看能不能有什么帮助吧。。。没有的话,下次努力
1.导入系统头文件 AVFoundation
2*.创建界面UI*
3.播放功能实现

在此要向各位推荐一下b站开源项目ijkplayer 。目前本人用得就是此开源框架,它是对ffmpeg的封装,能满足直播(拉流用ijkplayer即可,推流可以用来疯框架 LFLiveKit)以及视频播放功能,

这里是一些重要的代码片段

系统自带的avplayer的创建初始化

 //创建player
    *fileprivate func createPlayer() {
        playerItem = AVPlayerItem.init(url: NSURL.init(string: "http://krtv.qiniudn.com/150522nextapp") as! URL)
        playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        player = AVPlayer.init(playerItem: playerItem)
        let playerLayer = AVPlayerLayer.init(player: player)
        playerLayer.frame = CGRect.init(x: 0, y: 0, width: screenW, height: screenH)
        playerLayer.videoGravity = AVLayerVideoGravityResize
        self.view.layer.addSublayer(playerLayer)
        player.play()

        addTimer()*

    }

下一个视频的实现。有兴趣可以试试只换url会产生什么效果.

//下一曲,这里应该重新创建  AVPlayerItem
    func nextVideo()  {
        //        player = nil
        player.pause()
        removeTimer()
        playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges", context: nil)
        playerItem = nil
        //        player = nil
        let playerI = AVPlayerItem.init(url: NSURL.init(string: "http://wvideo.spriteapp.cn/video/2016/0328/56f8ec01d9bfe_wpd.mp4") as! URL)
        //
        player.replaceCurrentItem(with: playerI)

        //        let play = AVPlayer.init(playerItem: playerI)
        //        player = play
        playerItem = playerI
        playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        player.play()
        addTimer()
    }

进度条,用观察者模式实现

func progressSliderValueChange()  {
        //        拖动改变视频播放进度
        if player.status == .readyToPlay {
            let total = Float(playerItem.duration.value/Int64(playerItem.duration.timescale))
            let dragedSeconds = floorf(total * progressSlider.value)

            let dragedCMTime = CMTime.init(value: CMTimeValue(dragedSeconds), timescale: 1)
            player.pause()
            removeTimer()
            player.seek(to: dragedCMTime, completionHandler: { (finish) in
                if finish  {
                    self.player.play()
                    self.addTimer()
                }
            })

        }
    }
    // 观察者模式 进行进度条的改变
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "loadedTimeRanges" {
            //            let timeinterval = self.availableDuration()//缓冲速度
            let duration = playerItem.duration
            let total = CMTimeGetSeconds(duration)
            progress.setProgress(Float(total), animated: false)
        }

    }

下面是计算时间的方法,可以不看,相信有很多大神比我写的好

 func availableDuration() -> TimeInterval{
        let loadedTimeRanges = player.currentItem?.loadedTimeRanges
        let timeRange = loadedTimeRanges?.first?.timeRangeValue as CMTimeRange?
        let startSeconds = CMTimeGetSeconds(timeRange!.start)
        let durationSeconds = CMTimeGetSeconds(timeRange!.duration)
        let result = (startSeconds + durationSeconds)
        return result

    }
    //计算时间的方法
    func timerProgress()  {
        if playerItem.duration.timescale != 0 {
            progressSlider.maximumValue = 1.0
            let total = Float(playerItem.duration.value/Int64(playerItem.duration.timescale))
            progressSlider.value = Float(CMTimeGetSeconds(playerItem.currentTime()))/total
            let promin = NSInteger(CMTimeGetSeconds(playerItem.currentTime()))/60//当前秒
            let prosec = NSInteger(CMTimeGetSeconds(playerItem.currentTime()))%60//当前分

            let durmin = NSInteger(total)/60
            let dursec = NSInteger(total)%60

            let leftmin = durmin - promin
            let leftsec = dursec - prosec


            timeLable.text = NSString.init(format: "%02ld:%02ld", promin,prosec,durmin,dursec) as String

            leftLable.text = NSString.init(format: "-%02ld:%02ld", leftmin,leftsec) as String

        }
    }

Demo 的 github 地址 : https://github.com/aasdsjk/AVPlayer
avplayer的简单使用大概就是这样的了,项目中用的是第三方。。。这个是自己有空写的一个简单的demo
有兴趣的可以看一看

另外推荐一个自己写的弹出自定义视图的工具,支持pod 给个Star

2017-06-17 21:18:12 yiyihuazi 阅读数 3499
过去我们可以使用 Media Player 框架 MPMoviePlayerController 来播放视频、音频。但自 iOS9.0 起,这个便被废除。取而代之的便是 AVFoundation 框架的 AVPlayer

1,AVPlayer介绍

(1)AVPlayer 可以用来播放视频,也可以播放任何 iOS 支持的音频。
(2)AVPlayer 既可以播放本地音频,可以播放网络音频(在线音频)。
(3)要注意的是,如果播放远程音频,AVPlayer 同样是全部加载到本地后才开始播放,而不是以流媒体的形式播放。

2,效果图

(1)下面使用 AVPlayer 制作一个音乐播放器。
(2)点击按钮可以时音乐在“播放”和“暂停”两个状态间切换。
(3)播放过程中进度条和旁边的标签会实时显示当前的进度。
(4)进度条滑块可以自由拖动,并播放对应时间点的音乐。
原文:Swift - 使用AVPlayer制作一个音乐播放器1(带播放时间和播放进度)

3,样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import UIKit
import AVFoundation
 
class ViewControllerUIViewController {
     
    //播放按钮
    @IBOutlet weak var playButton: UIButton!
     
    //可拖动的进度条
    @IBOutlet weak var playbackSlider: UISlider!
     
    //当前播放时间标签
    @IBOutlet weak var playTime: UILabel!
     
    //播放器相关
    var playerItem:AVPlayerItem?
    var player:AVPlayer?
 
    override func viewDidLoad() {
        super.viewDidLoad()
         
        //初始化播放器
        let url = URL(string: "http://www.hangge.com/music.mp3")
        playerItem = AVPlayerItem(url: url!)
        player = AVPlayer(playerItem: playerItem!)
         
        //设置进度条相关属性
        let duration : CMTime = playerItem!.asset.duration
        let seconds : Float64 CMTimeGetSeconds(duration)
        playbackSlider!.minimumValue = 0
        playbackSlider!.maximumValue = Float(seconds)
        playbackSlider!.isContinuous = false
         
        //播放过程中动态改变进度条值和时间标签
        player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1),
                                        queue: DispatchQueue.main) { (CMTime) -> Void in
            if self.player!.currentItem?.status == .readyToPlay {
                //更新进度条进度值
                let currentTime = CMTimeGetSeconds(self.player!.currentTime())
                self.playbackSlider!.value = Float(currentTime)
                 
                //一个小算法,来实现00:00这种格式的播放时间
                let all:Int=Int(currentTime)
                let m:Int=all % 60
                let f:Int=Int(all/60)
                var time:String=""
                if f<10{
                    time="0\(f):"
                }else {
                    time="\(f)"
                }
                if m<10{
                    time+="0\(m)"
                }else {
                    time+="\(m)"
                }
                //更新播放时间
                self.playTime!.text=time
            }
        }
    }
     
    //播放按钮点击
    @IBAction func playButtonTapped(_ sender: Any) {
        //根据rate属性判断当天是否在播放
        if player?.rate == 0 {
            player!.play()
            playButton.setTitle("暂停"for: .normal)
        else {
            player!.pause()
            playButton.setTitle("播放"for: .normal)
        }
    }
     
    //拖动进度条改变值时触发
    @IBAction func playbackSliderValueChanged(_ sender: Any) {
        let seconds : Int64 Int64(playbackSlider.value)
        let targetTime:CMTime CMTimeMake(seconds, 1)
        //播放器定位到对应的位置
        player!.seek(to: targetTime)
        //如果当前时暂停状态,则自动播放
        if player!.rate == 0
        {
            player?.play()
            playButton.setTitle("暂停"for: .normal)
        }
    }
     
    //页面显示时添加歌曲播放结束通知监听
    override func viewWillAppear(_ animated: Bool) {
        NotificationCenter.default.addObserver(self, selector: #selector(finishedPlaying),
            name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
    }
     
    //页面消失时取消歌曲播放结束通知监听
    override func viewWillDisappear(_ animated: Bool) {
        NotificationCenter.default.removeObserver(self)
    }
     
    //歌曲播放完毕
    func finishedPlaying(myNotification:NSNotification) {
        print("播放完毕!")
        let stopedPlayerItem: AVPlayerItem = myNotification.object asAVPlayerItem
        stopedPlayerItem.seek(to: kCMTimeZero)
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
源码下载hangge_1668.zip

原文出自:www.hangge.com  转载请保留原文链接:http://www.hangge.com/blog/cache/detail_1668.html
2015-10-22 15:35:12 linweicanpc2 阅读数 1133


1.当app切换到后台,在播的视频会被暂停,avPlayer.rate的值为0,如果正常播放,rate的值为1。可通过监听UIApplicationWillEnterForegroundNotification通知,回到前台时判读已被中断则继续进行。

2.观察者重复移除(removeObserver)监听会报错,解决方法:try {removeObserver}

3.只有在playerItem.status == AVPlayerItemStatus.ReadyToPlay的时候,才能seekToTime

4.监听播放进度,avPlayer.addPeriodicTimeObserverForInterval

func addPeriodicTimeObserverForInterval(interval: CMTime, queue: dispatch_queue_t!, usingBlock block: ((CMTime) -> Void)!) -> AnyObject!

5.获取已缓冲完成的可播放时长(以秒为单位)   

class func getAvailableDuration(playerItem:AVPlayerItem) -> Double
    {
        var duration:Double = 0
        if let loadedTimeRanges = playerItem.loadedTimeRanges where loadedTimeRanges.count > 0
        {
            var timeRange:CMTimeRange = loadedTimeRanges[0].CMTimeRangeValue
            var startSeconds:Double = CMTimeGetSeconds(timeRange.start)
            var durationSeconds:Double = CMTimeGetSeconds(timeRange.duration)
            duration = startSeconds + durationSeconds
        }
        return duration
    }









2016-06-27 18:16:16 u012297622 阅读数 7087

播放器

对于资源的播放,你应使用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")
    }
2016-12-28 17:20:38 longshihua 阅读数 8506
AVplayer

An AVPlayer is a controller object used to manage the playback and timing of a media asset. It provides the interface to control the player’s transport behavior such as its ability to play, pause, change the playback rate, and seek to various points in time within the media’s timeline. You can use an AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTP Live Streaming.

AVPlayer是一个控制对象用于管理媒体asset的播放,它提供了相关的接口控制播放器的行为,比如:播放、暂停、改变播放的速率、跳转到媒体时间轴上的某一个点(简单理解就是实现拖动功能显示对应的视频位置内容)。我们能够使用AVPlayer播放本地和远程的媒体文件(使用 HTTP Live Streaming),比如: QuickTime movies 和 MP3 audio files,所以AVPlayer可以满足音视频播放的基本需求。

注意:
1:AVPlayer继承NSObject,所以单独使用AVPlayer时无法显示视频的,必须将视频图层添加到AVPlayerLayer中方能显示视频。
2:AVPlayer一次只能播放单一的媒体资源(asset),但是player实例对象能够被重复用于播放其它媒体资源,可以调用replaceCurrentItem(with:)方法更新当前播放资源。如果想播放多个资源,我们可以使用AVPlayer的子类AVQueuePlayer,该类能够创建和管理多个媒体资源列队,

AVPlayer是一个动态对象,它的状态不断改变,有两种方式我们能够观察播放器的状态:

1:General State Observations: You can use Key-value observing (KVO) to observe state changes to many of the player’s dynamic properties, such as its currentItem or its playback rate. You should register and unregister for KVO change notifications on the main thread. This avoids the possibility of receiving a partial notification if a change is being made on another thread. AVFoundation invokes observeValue(forKeyPath:of:change:context:) on the main thread, even if the change operation is made on another thread.

基本状态观察者:你能够使用KVO来观察player动态属性的状态改变,比如像: currentItem 或者它的播放速度。我们应该在主线程注册和去除KVO,这能够避免如果在其它线程发送改变而导致接收局部通知,当发生通知,AVFoundation将在主线程调用observeValue(forKeyPath:of:change:context:) 方法,即使是在其他线程发生。

2:Timed State Observations: KVO works well for general state observations, but isn’t intended for observing continuously changing state like the player’s time. AVPlayer provides two methods to observe time changes:addPeriodicTimeObserver(forInterval:queue:using:)addBoundaryTimeObserver(forTimes:queue:using:)
These methods let you observe time changes either periodically or by boundary, respectively. As changes occur, the callback block or closure you supply to these methods is invoked giving you the opportunity to take some action such as updating the state of your player’s user interface.

时间状态观察者:KVO能够很好的观察生成的状态,但是并不能够观察播放时间的改变,所以AVPlayer提供了两个方法来观察时间的改变,addPeriodicTimeObserver(forInterval:queue:using:)addBoundaryTimeObserver(forTimes:queue:using:),这两个方法能够让我们周期性或者边界方法进行观察,当改变发生,我们所提供的回调block或者闭包将会被触发,在闭包中我们将有机会更新播放器的状态和相关UI界面。

AVplayer和AVPlayerItem都是不可见对象,这因为意味着它们是不能够呈现视频在屏幕上,我们有两个基本方法在屏幕上显示视频:

1:AVKit: The best way to present your video content is by using the AVKit framework’s AVPlayerViewController class in iOS and tvOS or the AVPlayerView class in macOS. These classes present the video content, along with playback controls and other media features giving you a full-featured playback experience.

使用AVKit这是最好的方式呈现视频内容,我们只需要使用AVKit框架的AVPlayerViewController类,该类能够播放视频内容,并且带有相应的播放控件和一些其他的媒体特征,能够进行全屏播放。

2:AVPlayerLayer: If you are building a custom interface for your player, you use a Core Animation CALayer subclass provided by AVFoundation called AVPlayerLayer. The player layer can be set as a view’s backing layer or can be added directly to the layer hierarchy. Unlike AVPlayerView and AVPlayerViewController, a player layer doesn’t present any playback controls, but simply presents the visual content on screen. It is up to you to build the playback transport controls to play, pause, and seek through the media.

使用AVPlayerLayer,如果是为播放器创建自定义界面,我们能够使用核心动画CALayer的子类AVPlayerLayer来播放。该播放layer能够被设置为视图的backing layer或者直接添加到layer层级中。AVPlayerLayer只是简单的呈现视频内容,并不像AVPlayerView和AVPlayerViewController,是没有提供播放控件的。对于播放界面需要什么样的播放控件,完全取决于我们自己自定义界面实现播放、暂停、调整等一系列功能。

AVPlayerItem

AVPlayerItem:是代表一个AVAsset状态,可以使用它实时的观察到视频播放状态。管理着视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

AVPlayerItem存储了AVAsset 对象的引用,它代表被播放的媒体,如果你需要访问asset的信息,在它进入播放列队之前,我们能够使用AVAsynchronousKeyValueLoading 协议中的方法来加载我们所需要的值。也有可选的方法,那就是AVPlayerItem对象能够自动根据你传递给构造器方法init(asset:automaticallyLoadedAssetKeys:) 的参数来加载需要的asset数据,当AVPlayerItem是准备好进行播放,asset相关的属性都将被加载用于播放。

AVPlayerItem是动态对象,除了能够被改变的属性值之外,其它的可读属性值,会在AVPlayer播放期间发生改变。我们能够使用 Key-value observing来观察这些属性的改变,对于AVPlayerItem最重要的一个属性就是status。该属性指示是否playerItem已经准备好用于播放。事实上,当我们第一次创建playerItem的时候,该status属性是指为unknown,这意味着媒体并没有加载完成,还没有准备好播放。当为AVplayer关联该playerItem对象,那么playerItem的媒体资源将会立马准备用于播放。

AVPlayerItem属性解析:
 
loadedTimeRanges 该属性是一个数组,主要是包含已经下载的媒体数据,所提供的范围可能不连续。
playbackBufferEmpty:是否播放已经消耗了所有的缓存媒体数据,并且播放将结束
playbackLikelyToKeepUp:是否播放将继续不会停止
 
open var status:AVPlayerItemStatus{ get }该值是一个可观察属性,用于确定是否接受者能够播放,当值为failed时,接受者将不能够用于进行播放,需要创建一个新的实例取代。
 
AVPlayerItemStatus枚举值表面是否AVPlayerItem能够成功播放。
public enum  AVPlayerItemStatus :Int {
 case unknown    未知状态
 
case readyToPlay准备播放状态,表示player item准备被播放
 
case failed     失败状态
 }
 
open var error:Error? { get }如果接受者的状态是AVPlayerItemStatusFailed,该属性值可以描述失败的原因。
open var forwardPlaybackEndTime:CMTime跳到结束位置
open var reversePlaybackEndTime:CMTime跳到开始位置
open func seek(to time:CMTime)跳到指定位置

AVPlayerLayer

AVPlayerLayer:视频播放图层对象,它是需要添加到当前视图的图层上 。AVPlayerLayerCALayer的子类,使用AVPlayer对象创建AVPlayerLayer用于播放视频,注意:开发中,单纯使用AVPlayer类是无法显示视频的,要将视频层添加至AVPlayerLayer中,这样才能将视频显示出来,我们能够使用下面代码理解:

AVPlayer *player = <#A configured AVPlayer object#>;
CALayer *superlayer = <#Get a CALayer#>;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];

常用属性:

videoGravity 的属性,默认设置了AVLayerVideoGravityResize,查看该属性以及相关的其他属性值发现有3种值可以设置
AVLayerVideoGravityResizeAspect:按原视频比例显示,是竖屏的就显示出竖屏的,两边留黑
AVLayerVideoGravityResizeAspectFill:以原比例拉伸视频,直到两边屏幕都占满,但视频内容有部分就被切割了
AVLayerVideoGravityResize:是拉伸视频内容达到边框占满,但不按原比例拉伸,这里明显可以看出宽度被拉伸了

AVAsset

AVAsset代表一个抽象的媒体,包括标题,文件大小等等,不关联任何格式,每个AVAsset由多个track组成,每个track可以是一个音频通道或者视频通道,经常使用AVAsset的子类AVURLAsset初始化asset,传入URL,该URL引用了视听媒体的资源,比如:stream(包括:HTTP live streams), QuickTime电影文件,MP3文件,和其它格式的文件,我们也可以使用其它具体的子类来初始化asset,具体子类扩大了视听媒体有用方式的基本模型,比如:AVComposition用于处理临时的编辑
 
为了播放AVAsset实例,需要初始化AVPlayerItem,使用player item来建立AVAsset的呈现状态(比如:是否在被播放的时候仅仅只限制asset的缓存范围),而且为AVplayer对象提供该player item对象用于播放,或者组合多个player item

为了收集一个或者多个资源asset的视听数据结构,我们可以插入AVAsset对象到AVMutableComposition

使用AVplayer播放本地、网络视频

AVPlayer视频播放基本步骤
1:创建视频资源地址URL,可以是网络URL
2:通过URL创建视频内容对象AVPlayerItem,一个视频对应一个AVPlayerItem
3:创建AVPlayer视频播放器对象,需要一个AVPlayerItem进行初始化
4:创建AVPlayerLayer播放图层对象,添加到显示视图上
5:播放器播放play,播放器暂停pause
6:添加通知中心监听视频播放完成,使用KVO监听播放内容的属性变化
7:进度条监听是调用AVPlayer的对象方法:open func addPeriodicTimeObserver(forInterval interval:CMTime, queue:DispatchQueue?, using block:@escaping(CMTime) -> Swift.Void) ->Any

 1:播放/暂停
 
使用视频URL初始化一个AVPlayerItem,把AVPlayerItem设置为AVPlayercurrentItem,然后通过KVO监听AVPlayerItem的属性playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil),当属性变为AVPlayerStatusReadyToPlay时,通过AVPlayer调用play方法即可播放视频。

AVPlayerplaypause分别控制播放和暂停,根据AVPlayer的播放速度rate可以判断当前是否为播放状态,rate=0暂停,rate=1播放。视频播放完成后AVPlayerItem会发送AVPlayerItemDidPlayToEndTimeNotification通知。

 2:视频时间

视频时间包含两部分:视频总时间和视频当前播放时间。视频总时间通过CMTimeGetSeconds(player.currentItem.duration)获取,当前播放时间通过CMTimeGetSeconds(player.currentTime)获取。

获取到这两个时间,就可以展示视频播放进度。播放进度需要一秒更新一次,可以用定时器来更新,也可以用AVPlayer的方法 addPeriodicTimeObserver(forInterval interval:CMTime, queue:DispatchQueue?, using block:@escaping(CMTime) -> Swift.Void) -> Any在闭包里更新。建议使用这个方法更新时间,因为它更新时间更加准确,使用闭包的参数time获得准确的播放进度。


具体代码实现和效果


视图控制器中拥有如下几个属性,在viewDidLoad中创建UI布局

class SHAVPlayerController: UIViewController {
    
    //播放器容器
    var containerView: UIView!
    //播放/暂停按钮
    var playOrPauseButton: UIButton!
    //播放进度
    var progress: UIProgressView!
    //显示播放时间
    var timeLabel: UILabel!
    
    //播放器对象
    var player: AVPlayer?
    //播放资源对象
    var playerItem: AVPlayerItem?
    //时间观察者
    var timeObserver: Any!

    override func viewDidLoad() {
        super.viewDidLoad()
        createUI()
    }
    
}

UI布局,containerView创建容器视图用于显示视频,playOrPauseButton播放、暂停按钮用于控制视频的播放和暂停;progress进度条显示视频当前的播放进度;时间timelabel显示当前的播放时间.

   //UI布局
    func createUI(){
        containerView = UIView(frame: CGRect(x: 0, y: 100, width: self.view.width, height: 200))
        containerView.backgroundColor = UIColor.gray
        view.addSubview(containerView)
        
        playOrPauseButton = UIButton(type: .custom)
        playOrPauseButton.frame = CGRect(x: containerView.x + 10, y: containerView.bottom + 5, width: 16, height: 16)
        playOrPauseButton.setImage(UIImage(named:"player_play"), for: .normal)
        playOrPauseButton.addTarget(self, action: #selector(SHAVPlayerController.playOrPauseButtonClicked(button:)), for: .touchUpInside)
        view.addSubview(playOrPauseButton)
        
        progress = UIProgressView(frame: CGRect(x: playOrPauseButton.right + 5, y: playOrPauseButton.y, width: 220, height: 20))
        progress.centerY = playOrPauseButton.centerY;
        progress.progressTintColor = UIColor.blue
        progress.trackTintColor = UIColor.gray
        view.addSubview(progress)
        
        timeLabel = UILabel(frame: CGRect(x: progress.right + 5, y: playOrPauseButton.y, width: 60, height: 20))
        timeLabel.font = UIFont.systemFont(ofSize: 12.0)
        timeLabel.textColor = UIColor.red
        timeLabel.centerY = playOrPauseButton.centerY
        view.addSubview(timeLabel)
        
        addPlayerToAVPlayerLayer()
    }

获取本地资源,并添加观察者,等资源准备完毕,开始播放

    func addPlayerToAVPlayerLayer(){
        //获取本地视频资源
        guard let path = Bundle.main.path(forResource: "1", ofType: ".mp4") else {
            return
        }
        //播放本地视频
        let url = NSURL(fileURLWithPath: path)
        //播放网络视频
        // let url = NSURL(string: path)!
        playerItem = AVPlayerItem(url: url as URL)
        player = AVPlayer(playerItem: self.playerItem)
        
        //创建视频播放器图层对象
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = CGRect(x: containerView.x, y: 0, width: containerView.width, height: containerView.height)
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspect //视频填充模式
        containerView.layer.addSublayer(playerLayer)
        
        addProgressObserver()
        addObserver()
    }

AVPlayer提供了一种可以让我们周期性调用的一个block,在该block内我们可以实现进度的更新,block返回的值我们必须强引用,并且在不需要的时候去除时间观察者。第一个参数的调用的时间间隔,使用CMTime表示时间,不直接使用秒数,是考虑到播放的速率,value/timescale = seconds.第二个参数,我们需要传入串行列队,即主线程列队。CMTime(1,1); CMTime(2,2);均可以表示1秒,但是播放速率不一样。这里需要注意self的循环引用,所以使用[weakself]进行弱引用。

  //给播放器添加进度更新
    func addProgressObserver(){
    //这里设置每秒执行一次.
    timeObserver =  player?.addPeriodicTimeObserver(forInterval: CMTimeMake(Int64(1.0), Int32(1.0)), queue: DispatchQueue.main) { [weak self](time: CMTime) in
                //CMTimeGetSeconds函数是将CMTime转换为秒,如果CMTime无效,将返回NaN
                let currentTime = CMTimeGetSeconds(time)
                let totalTime = CMTimeGetSeconds(self!.playerItem!.duration)
                //更新显示的时间和进度条
                self!.timeLabel.text = self!.formatPlayTime(seconds: CMTimeGetSeconds(time))
                self!.progress.setProgress(Float(currentTime/totalTime), animated: true)
                print("当前已经播放\(self!.formatPlayTime(seconds: CMTimeGetSeconds(time)))")
        }
    }
    
    //给AVPlayerItem、AVPlayer添加监控
    func addObserver(){
        //为AVPlayerItem添加status属性观察,得到资源准备好,开始播放视频
        playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
        //监听AVPlayerItem的loadedTimeRanges属性来监听缓冲进度更新
        playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SHAVPlayerController.playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
    }

监听playerItem状态,控制播放,播放结束回到最开始位置

    ///  通过KVO监控播放器状态
    ///
    /// - parameter keyPath: 监控属性
    /// - parameter object:  监视器
    /// - parameter change:  状态改变
    /// - parameter context: 上下文
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let object = object as? AVPlayerItem  else { return }
        guard let keyPath = keyPath else { return }
        if keyPath == "status"{
            if object.status == .readyToPlay{ //当资源准备好播放,那么开始播放视频
              player?.play()
              print("正在播放...,视频总长度:\(formatPlayTime(seconds: CMTimeGetSeconds(object.duration)))")
            }else if object.status == .failed || object.status == .unknown{
              print("播放出错")
            }
        }else if keyPath == "loadedTimeRanges"{
            let loadedTime = avalableDurationWithplayerItem()
            print("当前加载进度\(loadedTime)")
        }
    }
    
    //将秒转成时间字符串的方法,因为我们将得到秒。
    func formatPlayTime(seconds: Float64)->String{
        let Min = Int(seconds / 60)
        let Sec = Int(seconds.truncatingRemainder(dividingBy: 60))
        return String(format: "%02d:%02d", Min, Sec)
    }
    
    //计算当前的缓冲进度
    func avalableDurationWithplayerItem()->TimeInterval{
        guard let loadedTimeRanges = player?.currentItem?.loadedTimeRanges,let first = loadedTimeRanges.first else {fatalError()}
        //本次缓冲时间范围
        let timeRange = first.timeRangeValue
        let startSeconds = CMTimeGetSeconds(timeRange.start)//本次缓冲起始时间
        let durationSecound = CMTimeGetSeconds(timeRange.duration)//缓冲时间
        let result = startSeconds + durationSecound//缓冲总长度
        return result
    }
    
    //播放结束,回到最开始位置,播放按钮显示带播放图标
    func playerItemDidReachEnd(notification: Notification){
        player?.seek(to: kCMTimeZero, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
        progress.progress = 0.0
        playOrPauseButton.setImage(UIImage(named:"player_play"), for: .normal)
    }

播放、暂停按钮响应事件

//点击播放/暂停按钮
    func playOrPauseButtonClicked(button: UIButton){
     if let player = player{
        if player.rate == 0{//点击时已暂停
           button.setImage(UIImage(named:"player_pause"), for: .normal)
           player.play()
        }else if player.rate == 1{//点击时正在播放
           player.pause()
           button.setImage(UIImage(named:"player_play"), for: .normal)
        }
      }
    }
最后,去除监听观察者

 //去除观察者
    func removeObserver(){
        playerItem?.removeObserver(self, forKeyPath: "status")
        playerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges")
        player?.removeTimeObserver(timeObserver)
        NotificationCenter.default.removeObserver(self, name:  Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
    }
    
    deinit {
        removeObserver()
    }


参考