AVPlayer 音频播放器
-
AVPlayer 是管理媒体资产的播放和时间的控制器对象。使用AVPlayer实例播放基于文件的本地和远程视听媒体资源。
-
AVAsset 是一个视听媒体模型对象,视听媒体模型基于文件的媒体,如QuickTime电影或MP3音频文件,以及使用HTTP实时流(HLS)流式传输的媒体。视听媒体模型是的一个或多个视听媒体轨道(
AVAssetTrack
)实例的容器对象。 -
AVAssetTrack 视听媒体轨道实例对统一类型的媒体轨道进行建模。最常用的轨道类型是音频和视频,但资产也可能包含补充轨道,如隐藏式字幕、字幕和计时元数据。
- 认识播放器
AVPlayer
- 认识播放对象
AVPlayerItem
- 认识试听媒体资产
AVAsset
- 播放器
AVPlayer
的初始化 - 监听
AVPlayer
的播放进度 - 设置切换播放对象
- 监听 AVPlayerItem 属性
- 切换播放速率
- 播放中心概览及控制
初始化 Player
1private var player: AVPlayer = AVPlayer(playerItem: nil)
监听 AVPlayer 属性
1/// 添加 player 监听
2private func addPlayerObserver(){
3 player.addObserver(self, forKeyPath: "timeControlStatus", options: .new, context: nil)
4 let interval = CMTimeMakeWithSeconds(1.0, preferredTimescale: 600)
5 timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
6 if self.player.currentItem?.status == .readyToPlay {
7 self.playerTimePlayChange(time: time)
8 }
9 }
10}
11
12/// 移除 player 监听
13private func removePlayerObserver(){
14 player.removeObserver(self, forKeyPath: "timeControlStatus")
15 if let token = timeObserverToken {
16 player.removeTimeObserver(token)
17 timeObserverToken = nil
18 }
19}
设置播放数据
使用 AVPlayer 一次播放单个媒体资产。您可以使用其 replaceCurrentItem(with:)
方法重复使用播放器实例来播放其他媒体资产,但它一次只管理单个媒体资产的播放。每次播放新的媒体资产时,都需要重新设置。
1/// 设置 player 的播放数据
2///
3/// 在这里可以重置 audio 相关的控件
4private func setupPlay(){
5 // 重置播放器的进度控制数据
6 audioSlider.updatePlayTime(0, totalTime: 0)
7 // 重置播放按钮状态
8 playBtn.isSelected = false
9 guard let model = self.audioDeatilModel else { return }
10 if let url = URL(string: model.playUrl) {
11 setupPlayerItem(url: url)
12 }else{
13 debugPrint("无效的播放地址")
14 }
15}
16
17private func setupPlayerItem(url: URL){
18 // 移除之前的 AVPlayerItem 监听
19 removePlayerItemObserver()
20 // 创建 AVPlayerItem
21 let item = AVPlayerItem(asset: AVAsset(url: url))
22 // 设置 AVPlayerItem 监听
23 addPlayerItemObserver(item: item)
24 // 将 AVPlayerItem 设置到播放器
25 player.replaceCurrentItem(with: item)
26}
Note
如果需要设置 url 的请求头请使用
AVURLAsset
来加载媒体资源。
AVURLAsset(url: url, options: [“AVURLAssetHTTPHeaderFieldsKey”:[“Referer”: “*.caigou2003.com”]])
这是苹果未公开的 API
监听 AVPlayerItem 的属性
1/// 添加 playerItem 监听
2private func addPlayerItemObserver(item: AVPlayerItem){
3 /// 添加加载状态
4 item.addObserver(self, forKeyPath: "status", options: .new, context: nil)
5 /// 添加缓存状态
6 item.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
7 /// 添加播放完成状态
8 NotificationCenter.default.addObserver(self, selector: #selector(playEnd), name: .AVPlayerItemDidPlayToEndTime, object: item)
9}
10
11/// 移除 playerItem 监听
12private func removePlayerItemObserver(){
13 // 移除加载状态监听
14 player.currentItem?.removeObserver(self, forKeyPath: "status")
15 // 移除缓存状态监听
16 player.currentItem?.removeObserver(self, forKeyPath: "loadedTimeRanges")
17 // 移除播放完成状态监听
18 NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
19}
重写自身的 observeValue 方法
1override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
2 if let playerItem = object as? AVPlayerItem {
3 if keyPath == "status" {
4 playerLoadStatusChange(PlayerItem: playerItem)
5 }else if keyPath == "loadedTimeRanges" {
6 playerLoadTimeRangesChange(PlayerItem: playerItem)
7 }
8 } else if let _ = object as? AVPlayer {
9 if keyPath == "timeControlStatus" {
10 playerTimeControlStatusChange()
11 }
12 }
13}
播放器的状态与进度
1/// AVPlayer 加载 AVPlayerItem 的状态
2/// - Parameter item: 加载中的 AVPlayerItem
3///
4/// 控制前进、回退、及进度控件的启用状态
5private func playerLoadStatusChange(PlayerItem item: AVPlayerItem){
6 self.setupSeekControl(isEnable: player.currentItem?.status == .readyToPlay)
7 switch player.currentItem?.status {
8 case .readyToPlay:
9 debugPrint("准备播放")
10 if let duration = player.currentItem?.duration {
11 audioSlider.updatePlayTime(0, totalTime: CMTimeGetSeconds(duration))
12 }
13 // 自动播放(列表循环播放时,音频数据准备好后不需要点播放按钮自动播放)
14 if playAuto {
15 playAudio()
16 }
17 case .failed:
18 debugPrint("加载失败")
19 case .unknown:
20 debugPrint("未知状态")
21 case .none:
22 debugPrint("空状态")
23 @unknown default:
24 fatalError()
25 }
26}
27
28/// AVPlayer 缓存 AVPlayerItem 的进度
29/// - Parameter item: 缓存中的 AVPlayerItem
30private func playerLoadTimeRangesChange(PlayerItem item: AVPlayerItem){
31 if let timeRange = player.currentItem?.loadedTimeRanges.first?.timeRangeValue {
32 let startSeconds = CMTimeGetSeconds(timeRange.start)
33 let durationSeconds = CMTimeGetSeconds(timeRange.duration)
34 let bufferedDuration = startSeconds + durationSeconds
35 let totalDuration = CMTimeGetSeconds(item.duration)
36 let bufferProgress = bufferedDuration / totalDuration
37 let str = String(format: "缓存进度:%.0f", bufferProgress * 100) + "%"
38 debugPrint(str)
39 }
40}
41
42/// AVPlayer 的播放状态
43///
44/// 在这里更新播放按钮的 播放暂停状态
45private func playerTimeControlStatusChange(){
46 switch player.timeControlStatus {
47 case .paused:
48 debugPrint("AVPlayer: ->paused")
49 playBtn.isSelected = false
50 case .waitingToPlayAtSpecifiedRate:
51 debugPrint("AVPlayer: ->waiting")
52 playBtn.isSelected = false
53 case .playing:
54 debugPrint("AVPlayer: ->playing")
55 playBtn.isSelected = true
56 @unknown default:
57 fatalError()
58 }
59}
60
61/// AVPlayer 的播放时间
62///
63/// 在这里更新进度控件的信息, 如播放进度、播放时长、总时长
64/// - Parameter time: 播放时间
65private func playerTimePlayChange(time: CMTime){
66 let currentTime = CMTimeGetSeconds(time)
67 let totalTime = CMTimeGetSeconds(self.player.currentItem?.duration ?? CMTimeMake(value: 1, timescale: 1))
68 debugPrint("当前播放时间: \(currentTime) 秒, 总时长: \(totalTime) 秒")
69 if !audioSlider.isTracking && !isSeeking {
70 self.audioSlider.updatePlayTime(currentTime, totalTime: totalTime)
71 }
72
73 // 定时关闭
74 if let date = playEndDate, date.timeIntervalSince1970 - Date().timeIntervalSince1970 <= 0 {
75 pauseAudio()
76 clearClokcInfo()
77 }
78}
播放器的控制
1/// 播放
2public func playAudio(){
3 if player.currentItem?.status == .readyToPlay {
4 player.play()
5 /// 设置播放速率
6 player.rate = playRate
7 }
8}
9
10/// 暂停
11public func pauseAudio(){
12 player.pause()
13}
14
15/// 快进到指定位置
16/// - Parameter tipositionTime: 目标位置 秒
17private func seekJump(PositionTime positionTime: TimeInterval) {
18 let time = CMTime(seconds: positionTime, preferredTimescale: 600)
19 setupSeekControl(isEnable: false)
20 self.isSeeking = true
21 player.seek(to: time) { isFinish in
22 self.isSeeking = false
23 self.setupSeekControl(isEnable: true)
24 }
25}
26
27/// 跳到播放位置
28/// - Parameter value: 目标位置 0~1
29///
30/// 这是以播放进度为基准的跳转播放
31private func seekJump(Progress value: Float){
32 if let playItem = player.currentItem {
33 let totalTime = playItem.duration.seconds
34 if totalTime.isNaN { return }
35 let targetTime = totalTime * value.double
36 audioSlider.updatePlayTime(targetTime, totalTime: totalTime)
37 self.seekJump(PositionTime: targetTime)
38 }
39}
40
41/// 跳到指定位置
42/// - Parameter time: 目标位置 秒
43///
44/// 这是以步进时间为基准的跳转播放
45/// 例如:快进10秒,则调用 seekJump(StepTime: 10),快退5秒,则调用 seekJump(StepTime: -5)
46private func seekJump(StepTime stepTime: Double) {
47 if let playItem = player.currentItem {
48 let totalTime = playItem.duration.seconds
49 if totalTime.isNaN { return }
50 let currentTime = player.currentTime().seconds
51 var targetTime = currentTime + stepTime
52 if targetTime < 0 { targetTime = 0 }
53 if targetTime > totalTime { targetTime = totalTime }
54 self.seekJump(PositionTime: targetTime)
55 }
56}